hibernate注解学习详细
hibernate注解(一)
在注解大行其道的今天,xml配置方式已经渐渐退化为非主流了,虽然注解有着很多优点,如:简化配置、减少配置文件数量、提高代码可读性,但它仍然有着无法媲美xml的地方。
注解虽然配置简单易懂,但是对于复杂点的数据结构,配置起来反而比xml难(例如用xml配置,因为有帮助文件dtd的支持,因此能很好的利用提示来帮助编程,而注解代码提示不会呈现出项目中所有相关的类,如果你在不了解注解每个类的名称和意义的情况下,想靠提示编程是很困难的,而且调试起来也比xml要麻烦多了),注解对于hibernate高级技术的支持也没有xml全面。
因此要想应对复杂的业务和数据结构,要求对注解的掌握要比xml的高,这样才能得心应手。
今天我力图较为全面的为大家介绍下hibernate注解。(hibernate注解配置不是本篇的重点,大家自行从网上找找吧)。
一、注解类
1. @Entity
将一个类声明为一个实体bean(即一个持久化POJO类)。
2. @Table
声明了该实体bean映射指定的表(table),目录(catalog)和schema名字
3. @Id
声明了该实体bean的标识属性(对应表中的主键)。
4. @Column
声明了属性到列的映射。该注解有如下的属性:
name 可选,列名(默认值是属性名)
unique 可选,是否在该列上设置唯一约束(默认值false)
nullable 可选,是否设置该列的值可以为空(默认值false)
insertable 可选,该列是否作为生成的insert语句中的一个列(默认值true)
updatable 可选,该列是否作为生成的update语句中的一个列(默认值true)
columnDefinition 可选,为这个特定列覆盖sql ddl片段(这可能导致无法在不同数据库间移植)
table 可选,定义对应的表(默认为主表)
length 可选,列长度(默认值255)
precision 可选,列十进制精度(decimal precision)(默认值0)
scale 可选,如果列十进制数值范围(decimal scale)可用,在此设置(默认值0)
5. @GeneratedValue
声明了主键的生成策略。该注解有如下属性:
strategy 指定生成的策略(JPA定义的),这是一个GenerationType。默认是GenerationType. AUTO
GenerationType.AUTO 主键由程序控制
GenerationType.TABLE 使用一个特定的数据库表格来保存主键
GenerationType.IDENTITY 主键由数据库自动生成(主要是自动增长类型)
GenerationType.SEQUENCE 根据底层数据库的序列来生成主键,条件是数据库支持序列。这个值要与generator一起使用,generator 指定生成主键使用的生成器(可能是orcale中的序列)。
6. SequenceGenerator
声明了一个数据库序列。该注解有如下属性:
name 表示该表主键生成策略名称,它被引用在@GeneratedValue中设置的“gernerator”值中
sequenceName 表示生成策略用到的数据库序列名称。
initialValue 表示主键初始值,默认为0。
allocationSize 每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50。
7. GenericGenerator
声明了一个hibernate的主键生成策略。支持十三种策略。该注解有如下属性:
name 指定生成器名称。
strategy 指定具体生成器的类名(指定生成策略)。
parameters 得到strategy指定的具体生成器所用到的参数。
其十三种策略(strategy属性的值)如下:
● native
对于orcale采用Sequence方式,对于MySQL和SQL Server采用identity(处境主键生成机制),native就是将主键的生成工作将由数据库完成,hibernate不管(很常用)
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "native")
● uuid
采用128位的uuid算法生成主键,uuid被编码为一个32位16进制数字的字符串。占用空间大(字符串类型)。
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "uuid")
● hilo
要在数据库中建立一张额外的表,默认表名为hibernate_unque_key,默认字段为integer类型,名称是next_hi(比较少用)。
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "hilo")
● assigned
在插入数据的时候主键由程序处理(很常用),这是<generator>元素没有指定时的默认生成策略。等同于JPA中的AUTO。
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "assigned")
● identity
使用SQL Server和MySQL的自增字段,这个方法不能放到Oracle中,Oracle不支持自增字段,要设定sequence(MySQL和SQL Server中很常用)。等同于JPA中的IDENTITY
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "identity")
● select
使用触发器生成主键(主要用于早期的数据库主键生成机制,少用)
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "select")
● sequence
调用谨慎数据库的序列来生成主键,要设定序列名,不然hibernate无法找到。
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "sequence", parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
● seqhilo
通过hilo算法实现,但是主键历史保存在Sequence中,适用于支持Sequence的数据库,如Orcale(比较少用)。
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "seqhilo", parameters = { @Parameter(name = "max_lo", value = "5") })
● increnment
插入数据的时候hibernate会给主键添加一个自增的主键,但是一个hibernate实例就维护一个计数器,所以在多个实例运行的时候不能使用这个方法。
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "increnment")
● foreign
使用另一个相关的对象的主键。通常和@OneToOne联合起来使用。
例:@Id
@GeneratedValue(generator = "idGenerator")
@GenericGenerator(name = "idGenerator", strategy = "foreign", parameters = { @Parameter(name = "property", value = "info") })
Integer id;
@OneToOne
EmployeeInfo info;
● guid
采用数据库底层的guid算法机制,对应MySQL的uuid()函数,SQL Server的newid()函数,ORCALE的rawtohex(sys_guid())函数等。
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "guid")
● uuid.hex
参见uuid,建议用uuid替换。
● sequence-identity
sequence策略的扩展,采用立即检索策略来获取sequence值,需要JDBC3.0和JDK4以上(含1.4)版本。
例:@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator", strategy = "sequence-identity", parameters = { @Parameter(name = "sequence", value = "seq_payablemoney") })
8. @Transient
声明了非持久化属性,即数据库中没有相应的映射字段,是一个普通属性。
9. @Temporal
声明了日期类型。
TemporalType.DATE 日期,例:2011-04-12
TemporalType.TIME 时间,例:22:50:30
TemporalType.TIMESTAMP 日期和时间,例:2011-04-12 22:51:30
10. @Version
声明了对乐观锁定的支持
例:@Version
@Column(name="OPTLOCK")
public Integer getVersion() { ... }
上面这个例子中,version属性将映射到 OPTLOCK列,entity manager使用该字段来检测更新冲突(防止更新丢失,请参考last-commit-wins策略)。
根据EJB3规范,version列可以是numeric类型(推荐方式)也可以是timestamp类型。Hibernate支持任何自定义类型,只要该类型实现了UserVersionType。
下一篇中,我们继续介绍hibernate表关联的注解方式。
hibernate注解(二)
本篇将向大家介绍表关联的注解方式。
1. 一对一(One-to-one)
使用@OneToOne注解可以建立实体bean之间的一对一的关联.一对一关联有三种情况:一是关联的实体都共享同样的主键,二是其中一个实体通过外键关联到另一个实体的主键(注意要模拟一对一关联必须在外键列上添加唯一约束).三是通过关联表来保存两个实体之间的连接关系(注意要模拟一对一关联必须在每一个外键上添加唯一约束).
首先,我们通过共享主键来进行一对一关联映射:
- @Entity
- public class Body {
- @Id
- public Long getId() { return id; }
- @OneToOne(cascade = CascadeType.ALL)
- @PrimaryKeyJoinColumn
- public Heart getHeart() {
- return heart;
- }
- ...
- }
- @Entity
- public class Heart {
- @Id
- public Long getId() { ...}
- }
@Entity
public class Body {
@Id
public Long getId() { return id; }
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Heart getHeart() {
return heart;
}
...
}
@Entity
public class Heart {
@Id
public Long getId() { ...}
}
上面的例子通过使用注解@PrimaryKeyJoinColumn定义了一对一关联.
下面这个例子使用外键列进行实体的关联.
- @Entity
- public class Customer implements Serializable {
- @OneToOne(cascade = CascadeType.ALL)
- @JoinColumn(name="passport_fk")
- public Passport getPassport() {
- ...
- }
- @Entity
- public class Passport implements Serializable {
- @OneToOne(mappedBy = "passport")
- public Customer getOwner() {
- ...
- }
@Entity
public class Customer implements Serializable {
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="passport_fk")
public Passport getPassport() {
...
}
@Entity
public class Passport implements Serializable {
@OneToOne(mappedBy = "passport")
public Customer getOwner() {
...
}
上面这个例子中,Customer 通过Customer表中名为的passport_fk 外键列和 Passport关联.@JoinColumn注解定义了联接列(join column).该注解和@Column注解有点类似,但是多了一个名为referencedColumnName的参数. 该参数定义了所关联目标实体中的联接列.注意,当referencedColumnName关联到非主键列的时候,关联的目标类必须实现Serializable,还要注意的是所映射的属性对应单个列(否则映射无效).
一对一关联可能是双向的.在双向关联中,有且仅有一端是作为主体(owner)端存在的:主体端负责维护联接列(即更新).对于不需要维护这种关系的从表则通过mappedBy属性进行声明.mappedBy的值指向主体的关联属性.在上面这个例子中,mappedBy的值为 passport.最后,不必也不能再在被关联端(owned side)定义联接列了,因为已经在主体端进行了声明.
如果在主体没有声明@JoinColumn,系统自动进行处理:在主表(owner table)中将创建联接列,列名为:主体的关联属性名+下划线+被关联端的主键列名.在上面这个例子中是passport_id,因为Customer中关联属性名为passport,Passport的主键是id.
第三种方式也许是最另类的(通过关联表).
- @Entity
- public class Customer implements Serializable {
- @OneToOne(cascade = CascadeType.ALL)
- @JoinTable(name = "CustomerPassports",
- joinColumns = @JoinColumn(name="customer_fk"),
- inverseJoinColumns = @JoinColumn(name="passport_fk")
- )
- public Passport getPassport() {
- ...
- }
- @Entity
- public class Passport implements Serializable {
- @OneToOne(mappedBy = "passport")
- public Customer getOwner() {
- ...
- }
@Entity
public class Customer implements Serializable {
@OneToOne(cascade = CascadeType.ALL)
@JoinTable(name = "CustomerPassports",
joinColumns = @JoinColumn(name="customer_fk"),
inverseJoinColumns = @JoinColumn(name="passport_fk")
)
public Passport getPassport() {
...
}
@Entity
public class Passport implements Serializable {
@OneToOne(mappedBy = "passport")
public Customer getOwner() {
...
}
Customer通过名为 CustomerPassports的关联表和 Passport关联; 该关联表拥有名为passport_fk的外键列,该 外键指向Passport表,该信息定义为inverseJoinColumn的属性值, 而customer_fk外键列指向Customer表, 该信息定义为 joinColumns的属性值.
这种关联可能是双向的.在双向关联中, 有且仅有一端是作为主体端存在的:主体端负责维护联接列(即更新). 对于不需要维护这种关系的从表则通过mappedBy属性进行声明. mappedBy的值指向主体的关联属性. 在上面这个例子中,mappedBy的值为 passport. 最后,不必也不能再在被关联端(owned side)定义联接列了,因为已经在主体端进行了声明.
你必须明确定义关联表名和关联列名.
2. 多对一(Many-to-one)
在实体属性一级使用@ManyToOne注解来定义多对一关联:
- @Entity()
- public class Flight implements Serializable {
- @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
- @JoinColumn(name="COMP_ID")
- public Company getCompany() {
- return company;
- }
- ...
- }
@Entity()
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
其中@JoinColumn是可选的,关联字段默认值和一对一 (one to one)关联的情况相似, 列名为:主体的关联属性名+下划线+被关联端的主键列名. 在这个例子中是company_id, 因为关联的属性是company, Company的主键是id.
@ManyToOne注解有一个名为targetEntity的参数, 该参数定义了目标实体名.通常不需要定义该参数, 因为在大部分情况下默认值(表示关联关系的属性类型)就可以很好的满足要求了. 不过下面这种情况下这个参数就显得有意义了:使用接口作为返回值而不是常见的实体.
- @Entity()
- public class Flight implements Serializable {
- @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class )
- @JoinColumn(name="COMP_ID")
- public Company getCompany() {
- return company;
- }
- ...
- }
- public interface Company {
- ...
@Entity()
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
public interface Company {
...
对于多对一也可以通过关联表的方式来映射。 通过@JoinTable注解可定义关联表, 该关联表包含了指回实体表的外键(通过@JoinTable.joinColumns) 以及指向目标实体表的外键(通过@JoinTable.inverseJoinColumns).
- @Entity()
- public class Flight implements Serializable {
- @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
- @JoinTable(name="Flight_Company",
- joinColumns = @JoinColumn(name="FLIGHT_ID"),
- inverseJoinColumns = @JoinColumn(name="COMP_ID")
- )
- public Company getCompany() {
- return company;
- }
- ...
- }
@Entity()
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinTable(name="Flight_Company",
joinColumns = @JoinColumn(name="FLIGHT_ID"),
inverseJoinColumns = @JoinColumn(name="COMP_ID")
)
public Company getCompany() {
return company;
}
...
}
3. 一对多(One-to-many)
在属性级使用 @OneToMany注解可定义一对多关联.一对多关联可以是双向关联.
● 双向
在EJB3规范中多对一这端几乎总是双向关联中的主体(owner)端, 而一对多这端的关联注解为@OneToMany( mappedBy=... )
- @Entity
- public class Troop {
- @OneToMany(mappedBy="troop")
- public Set<Soldier> getSoldiers() {
- ...
- }
- @Entity
- public class Soldier {
- @ManyToOne
- @JoinColumn(name="troop_fk")
- public Troop getTroop() {
- ...
- }
@Entity
public class Troop {
@OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
Troop 通过troop 属性和Soldier建立了一对多的双向关联. 在mappedBy端不必也不能再定义任何物理映射
对于一对多的双向映射,如果要一对多这一端维护关联关系, 你需要删除mappedBy元素并将多对一这端的 @JoinColumn的insertable和updatable设置为false. 很明显,这种方案不会得到什么明显的优化,而且还会增加一些附加的UPDATE语句.
- @Entity
- public class Troop {
- @OneToMany
- @JoinColumn(name="troop_fk") //we need to duplicate the physical information
- public Set<Soldier> getSoldiers() {
- ...
- }
- @Entity
- public class Soldier {
- @ManyToOne
- @JoinColumn(name="troop_fk", insertable=false, updatable=false)
- public Troop getTroop() {
- ...
- }
@Entity
public class Troop {
@OneToMany
@JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
● 单向
通过在被拥有的实体端(owned entity)增加一个外键列来实现一对多单向关联是很少见的,也是不推荐的. 我们强烈建议通过一个联接表(join table)来实现这种关联(下一节会对此进行解释). 可以通过@JoinColumn注解来描述这种单向关联关系.
- @Entity
- public class Customer implements Serializable {
- @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
- @JoinColumn(name="CUST_ID")
- public Set<Ticket> getTickets() {
- ...
- }
- @Entity
- public class Ticket implements Serializable {
- ... //no bidir
- }
@Entity
public class Customer implements Serializable {
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
public Set<Ticket> getTickets() {
...
}
@Entity
public class Ticket implements Serializable {
... //no bidir
}
Customer通过 CUST_ID列和Ticket 建立了单向关联关系.
● 通过关联表处理单向关联
通过联接表处理单向一对多关联是首选方式.这种关联通过@JoinTable注解来进行描述.
- @Entity
- public class Trainer {
- @OneToMany
- @JoinTable(
- name="TrainedMonkeys",
- joinColumns = @JoinColumn( name="trainer_id"),
- inverseJoinColumns = @JoinColumn( name="monkey_id")
- )
- public Set<Monkey> getTrainedMonkeys() {
- ...
- }
- @Entity
- public class Monkey {
- ... //no bidir
- }
@Entity
public class Trainer {
@OneToMany
@JoinTable(
name="TrainedMonkeys",
joinColumns = @JoinColumn( name="trainer_id"),
inverseJoinColumns = @JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
@Entity
public class Monkey {
... //no bidir
}
上面这个例子中,Trainer通过 TrainedMonkeys表和 Monkey 建立了单向关联. 其中外键trainer_id关联到Trainer (joinColumns), 而外键monkey_id关联到 Monkey (inversejoinColumns).
● 默认处理机制
通过联接表来建立单向一对多关联不需要描述任何物理映射. 表名由以下三个部分组成:主表(owner table)表名+下划线+从表(the other side table)表名. 指向主表的外键名:主表表名+下划线+主表主键列名 指向从表的外键名:主表所对应实体的属性名+下划线+从表主键列名 指向从表的外键定义为唯一约束,用来表示一对多的关联关系.
- @Entity
- public class Trainer {
- @OneToMany
- public Set<Tiger> getTrainedTigers() {
- ...
- }
- @Entity
- public class Tiger {
- ... //no bidir
- }
@Entity
public class Trainer {
@OneToMany
public Set<Tiger> getTrainedTigers() {
...
}
@Entity
public class Tiger {
... //no bidir
}
上面这个例子中,Trainer和Tiger 通过联接表 Trainer_Tiger建立单向关联关系, 其中外键trainer_id关联到Trainer (主表表名, _(下划线), trainer id), 而外键trainedTigers_id关联到Tiger (属性名称, _(下划线), Tiger表的主键列名).
4. 多对多(Many-to-many)
你可以通过@ManyToMany注解可定义的多对多关联. 同时,你也需要通过注解@JoinTable描述关联表和关联条件. 如果是双向关联,其中一段必须定义为owner,另一端必须定义为inverse(在对关联表进行更新操作时这一端将被忽略):
- @Entity
- public class Employer implements Serializable {
- @ManyToMany(
- targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,
- cascade={CascadeType.PERSIST, CascadeType.MERGE}
- )
- @JoinTable(
- name="EMPLOYER_EMPLOYEE",
- joinColumns=@JoinColumn(name="EMPER_ID"),
- inverseJoinColumns=@JoinColumn(name="EMPEE_ID")
- )
- public Collection getEmployees() {
- return employees;
- }
- ...
- }
- @Entity
- public class Employee implements Serializable {
- @ManyToMany(
- cascade = {CascadeType.PERSIST, CascadeType.MERGE},
- mappedBy = "employees",
- targetEntity = Employer.class
- )
- public Collection getEmployers() {
- return employers;
- }
- }
@Entity
public class Employer implements Serializable {
@ManyToMany(
targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,
cascade={CascadeType.PERSIST, CascadeType.MERGE}
)
@JoinTable(
name="EMPLOYER_EMPLOYEE",
joinColumns=@JoinColumn(name="EMPER_ID"),
inverseJoinColumns=@JoinColumn(name="EMPEE_ID")
)
public Collection getEmployees() {
return employees;
}
...
}
@Entity
public class Employee implements Serializable {
@ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "employees",
targetEntity = Employer.class
)
public Collection getEmployers() {
return employers;
}
}
至此,我们已经展示了很多跟关联有关的声明定义以及属性细节. 下面我们将深入介绍@JoinTable注解,该注解定义了联接表的表名, 联接列数组(注解中定义数组的格式为{ A, B, C }), 以及inverse联接列数组. 后者是关联表中关联到Employee主键的列(the "other side").
正如前面所示,被关联端不必也不能描述物理映射: 只需要一个简单的mappedBy参数,该参数包含了主体端的属性名,这样就绑定双方的关系.
和其他许多注解一样,在多对多关联中很多值是自动生成. 当双向多对多关联中没有定义任何物理映射时,hibernate根据以下规则生成相应的值. 关联表名:主表表名+_下划线+从表表名, 关联到主表的外键名:主表名+_下划线+主表中的主键列名. 关联到从表的外键名:主表中用于关联的属性名+_下划线+从表的主键列名. 以上规则对于双向一对多关联同样有效.
- @Entity
- public class Store {
- @ManyToMany(cascade = CascadeType.PERSIST)
- public Set<City> getImplantedIn() {
- ...
- }
- }
- @Entity
- public class City {
- ... //no bidirectional relationship
- }
@Entity
public class Store {
@ManyToMany(cascade = CascadeType.PERSIST)
public Set<City> getImplantedIn() {
...
}
}
@Entity
public class City {
... //no bidirectional relationship
}
上面这个例子中,Store_Table作为联接表. Store_id列是联接到Store表的外键. 而implantedIn_id列则联接到City表.
当双向多对多关联中没有定义任何物理映射时, Hibernate根据以下规则生成相应的值 关联表名: :主表表名+_下划线+从表表名, 关联到主表的外键名:从表用于关联的属性名+_下划线+主表中的主键列名. 关联到从表的外键名:主表用于关联的属性名+_下划线+从表的主键列名. 以上规则对于双向一对多关联同样有效.
- @Entity
- public class Store {
- @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
- public Set<Customer> getCustomers() {
- ...
- }
- }
- @Entity
- public class Customer {
- @ManyToMany(mappedBy="customers")
- public Set<Store> getStores() {
- ...
- }
- }
@Entity
public class Store {
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public Set<Customer> getCustomers() {
...
}
}
@Entity
public class Customer {
@ManyToMany(mappedBy="customers")
public Set<Store> getStores() {
...
}
}
在上面这个例子中,Store_Customer作为联接表. stores_id列是联接到Store表的外键, 而customers_id列联接到City表.
5. 其他
① 集合类型
你可以对 Collection ,List (指有序列表, 而不是索引列表), Map和Set这几种类型进行映射. EJB3规范定义了怎么样使用@javax.persistence.OrderBy 注解来对有序列表进行映射: 该注解接受的参数格式:用逗号隔开的(目标实体)属性名及排序指令, 如firstname asc, age desc,如果该参数为空,则默认以id对该集合进行排序. 如果某个集合在数据库中对应一个关联表(association table)的话,你不能在这个集合属性上面使用@OrderBy注解. 对于这种情况的处理方法,请参考Hibernate Annotation Extensions. EJB3 允许你利用目标实体的一个属性作为Map的key, 这个属性可以用@MapKey(name="myProperty")来声明. 如果使用@MapKey注解的时候不提供属性名, 系统默认使用目标实体的主键. map的key使用和属性相同的列:不需要为map key定义专用的列,因为map key实际上就表达了一个目标属性。 注意一旦加载,key不再和属性保持同步, 也就是说,如果你改变了该属性的值,在你的Java模型中的key不会自动更新 (请参考Hibernate Annotation Extensions). 很多人被<map>和@MapKey弄糊涂了。 其他它们有两点区别.@MapKey目前还有一些限制,详情请查看论坛或者 我们的JIRA缺陷系统。 注意一旦加载,key不再和属性保持同步, 也就是说,如果你改变了该属性的值,在你的Java模型中的key不会自动更新. (Hibernate 3中Map支持的方式在当前的发布版中还未得到支持).
Hibernate将集合分以下几类.
语义 | Java实现类 | 注解 |
Bag 语义 | java.util.List, java.util.Collection | @org.hibernate.annotations.CollectionOfElements或@OneToMany或@ManyToMany |
List 语义 | java.util.List | (@org.hibernate.annotations.CollectionOfElements或@OneToMany或@ManyToMany)以及@org.hibernate.annotations.IndexColumn |
Set 语义 | java.util.Set | @org.hibernate.annotations.CollectionOfElements 或@OneToMany或@ManyToMany |
Map 语义 | java.util.Map | (@org.hibernate.annotations.CollectionOfElements或@OneToMany或@ManyToMany)以及(空或@org.hibernate.annotations.MapKey/MapKeyManyToMany(支持真正的map),或@javax.persistence.MapKey |
EJB3规范不支持原始类型,核心类型,嵌入式对象的集合.但是Hibernate对此提供了支持
- @Entity public class City {
- @OneToMany(mappedBy="city")
- @OrderBy("streetName")
- public List<Street> getStreets() {
- return streets;
- }
- ...
- }
- @Entity public class Street {
- public String getStreetName() {
- return streetName;
- }
- @ManyToOne
- public City getCity() {
- return city;
- }
- ...
- }
- @Entity
- public class Software {
- @OneToMany(mappedBy="software")
- @MapKey(name="codeName")
- public Map<String, Version> getVersions() {
- return versions;
- }
- ...
- }
- @Entity
- @Table(name="tbl_version")
- public class Version {
- public String getCodeName() {...}
- @ManyToOne
- public Software getSoftware() { ... }
- ...
- }
@Entity public class City {
@OneToMany(mappedBy="city")
@OrderBy("streetName")
public List<Street> getStreets() {
return streets;
}
...
}
@Entity public class Street {
public String getStreetName() {
return streetName;
}
@ManyToOne
public City getCity() {
return city;
}
...
}
@Entity
public class Software {
@OneToMany(mappedBy="software")
@MapKey(name="codeName")
public Map<String, Version> getVersions() {
return versions;
}
...
}
@Entity
@Table(name="tbl_version")
public class Version {
public String getCodeName() {...}
@ManyToOne
public Software getSoftware() { ... }
...
}
上面这个例子中,City 中包括了以streetName排序的Street的集合. 而Software中包括了以codeName作为 key和以Version作为值的Map.
除非集合为generic类型,否则你需要指定targetEntity. 这个注解属性接受的参数为目标实体的class.
② 用cascade实现传播性持久化
也许你已经注意到了cascade属性接受的值为CascadeType数组. 在EJB3中的cascade的概念和Hibernate中的传播性持久化以及cascade操作非常类似, 但是在语义上有细微的区别,支持的cascade类型也有点区别:
● CascadeType.PERSIST: 如果一个实体是受管状态, 或者当persist()函数被调用时, 触发级联创建(create)操作
● CascadeType.MERGE: 如果一个实体是受管状态, 或者当merge()函数被调用时, 触发级联合并(merge)操作
● CascadeType.REMOVE: 当delete()函数被调用时, 触发级联删除(remove)操作
● CascadeType.REFRESH: 当refresh()函数被调用时, 触发级联更新(refresh)操作
● CascadeType.ALL: 以上全部
③ 关联关系获取
通过Hibernate你可以获得直接或者延迟获取关联实体的功能. fetch参数可以设置为FetchType.LAZY 或者 FetchType.EAGER. EAGER通过outer join select直接获取关联的对象, 而LAZY(默认值)在第一次访问关联对象的时候才会触发相应的select操作. EJBQL提供了fetch关键字,该关键字可以在进行特殊查询的时候覆盖默认值. 这对于提高性能来说非常有效,应该根据实际的用例来判断是否选择fetch关键字.
④ 映射复合主键与外键
组合主键使用一个可嵌入的类作为主键表示,因此你需要使用@Id 和@Embeddable两个注解. 还有一种方式是使用@EmbeddedId注解.注意所依赖的类必须实现 serializable以及实现equals()/hashCode()方法.
- @Entity
- public class RegionalArticle implements Serializable {
- @Id
- public RegionalArticlePk getPk() { ... }
- }
- @Embeddable
- public class RegionalArticlePk implements Serializable { ... }
@Entity
public class RegionalArticle implements Serializable {
@Id
public RegionalArticlePk getPk() { ... }
}
@Embeddable
public class RegionalArticlePk implements Serializable { ... }
或者
- @Entity
- public class RegionalArticle implements Serializable {
- @EmbeddedId
- public RegionalArticlePk getPk() { ... }
- }
- public class RegionalArticlePk implements Serializable { ... }
@Entity
public class RegionalArticle implements Serializable {
@EmbeddedId
public RegionalArticlePk getPk() { ... }
}
public class RegionalArticlePk implements Serializable { ... }
@Embeddable 注解默认继承了其所属实体的访问类型, 除非显式使用了Hibernate的@AccessType注解(这个注解不是EJB3标准的一部分). 而@JoinColumns,即@JoinColumn数组, 定义了关联的组合外键(如果不使用缺省值的话). 显式指明referencedColumnNames是一个好的实践方式, 否则,Hibernate认为你使用的列顺序和主键声明的顺序一致.
- @Entity
- public class Parent implements Serializable {
- @Id
- public ParentPk id;
- public int age;
- @OneToMany(cascade=CascadeType.ALL)
- @JoinColumns ({
- @JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
- @JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
- @JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
- })
- public Set<Child> children; //unidirectional
- ...
- }
- @Entity
- public class Child implements Serializable {
- @Id @GeneratedValue
- public Integer id;
- @ManyToOne
- @JoinColumns ({
- @JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
- @JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
- @JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
- })
- public Parent parent; //unidirectional
- }
- @Embeddable
- public class ParentPk implements Serializable {
- String firstName;
- String lastName;
- ...
- }
@Entity
public class Parent implements Serializable {
@Id
public ParentPk id;
public int age;
@OneToMany(cascade=CascadeType.ALL)
@JoinColumns ({
@JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
@JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
@JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
})
public Set<Child> children; //unidirectional
...
}
@Entity
public class Child implements Serializable {
@Id @GeneratedValue
public Integer id;
@ManyToOne
@JoinColumns ({
@JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
@JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
@JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
})
public Parent parent; //unidirectional
}
@Embeddable
public class ParentPk implements Serializable {
String firstName;
String lastName;
...
}
hibernate注解(三)
我们继续介绍hibernate注解的相关内容
1. OneToOne懒加载问题
一对一注解时,若采用外键列进行实体的关联的话,懒加载问题是需要注意下的。如下:
Student表:
- id int not null
- name varchar(50) not null
- card_id int not null
id int not null
name varchar(50) not null
card_id int not null
Card表:
- id int not null
- card_no varchar() not null
id int not null
card_no varchar() not null
Student类
- public class Student {
- private int id;
- private String name;
- private Card card;
- ....
- @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
- @JoinColumn(name = "card_id")
- public Card getCard() {
- return card;
- }
- public void setCard(Card card) {
- this.card = card;
- }
- }
public class Student {
private int id;
private String name;
private Card card;
....
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "card_id")
public Card getCard() {
return card;
}
public void setCard(Card card) {
this.card = card;
}
}
Card类
- public class Card {
- private int id;
- private String cardNo;
- private Student student;
- ....
- @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "card")
- public Student getStudent() {
- return student;
- }
- public void setStudent(Student student) {
- this.student = student;
- }
- }
public class Card {
private int id;
private String cardNo;
private Student student;
....
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "card")
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
按照上述关系,运行代码后我们会发现,Student获取Card时可以懒加载,但Card获取Student时却无法懒加载,那么这是为什么呢?
原因:在Card表里由于没有关系字段,因此仅从Card表角度看无法知道拥有该卡的学生是谁(除非在Card表建立Student表的外键student_id,但由于冗余了关系字段,因此很少有人这么干吧【注意sql的join语句是考虑了两张表的】),而从Student角度不一样,由于含有card_id字段可能清楚知道该学生拥有一张卡。
正是由于上面的原因,因此当从Card获取Student时,hibernate为了确定Student表中到底有没有该Card,因此发了一条sql:select * from Student where card_id = ?,参数既是该Card的id,以此来维护Card与Student的关系。
还有一种解释,但需要理解hibernate的懒加载的机制:代理。
2. 懒加载原理
hibernate使用了代理(Proxy),对实体的调用会被代理接受和处理,hibernate可以设置这个代理被调用到的时候去加载数据,从而实现延迟加载。那么对于一个映射对象,要么它有值,要么它是null,对于null值建立代理是没多大作用的,而且也不能对null建立动态代理。那就是说hibernate在对延迟加载建立代理的时候要考虑这个映射的对象是否是null。如果是null不需要建立代理,直接把映射的值设置成null,如果映射的对象不为null,那么hibernate就建立代理对象。
简而言之,为null就不能懒加载,为代理对象才能懒加载。
那么解决上述问题的办法呢?
● Card类的getStudent改为manyToOne,并设置unique=true。缺点:需要将字段改为Set<Student>。
● 手动为Student建立代理对象。(不建议这么做,而且我也没试过)
● 采用no-proxy懒加载机制,对类用instrument进行增强,即用类增强器对二进制Class文件进行强化处理。(很少有人这么做,不推荐)
以下是利用ant调用hibernate类增强器对class文件进行强化处理,ant的build.xml脚本如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <project name="hibernatelazy" default="instrument" basedir=".">
- <property name="lib.dir" value="./lib"/>
- <property name="classes.dir" value="./classes"/>
- <path id="lib.class.path">
- <fileset dir="${lib.dir}">
- <include name="**/*.jar"/>
- </fileset>
- </path>
- <target name="instrument">
- <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
- <classpath path="${classes.dir}"/>
- <classpath refid="lib.class.path"/>
- </taskdef>
- <instrument verbose="true">
- <fileset dir="${classes.dir}/com/derek/known/hbm">
- <include name="Knownquestions.class"/>
- </fileset>
- </instrument>
- </target>
- </project>
<?xml version="1.0" encoding="UTF-8"?>
<project name="hibernatelazy" default="instrument" basedir=".">
<property name="lib.dir" value="./lib"/>
<property name="classes.dir" value="./classes"/>
<path id="lib.class.path">
<fileset dir="${lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="instrument">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
</taskdef>
<instrument verbose="true">
<fileset dir="${classes.dir}/com/derek/known/hbm">
<include name="Knownquestions.class"/>
</fileset>
</instrument>
</target>
</project>
其中注意:
<property name="lib.dir" value="./lib"/>所需的JAR文件路径。
<property name="classes.dir" value="./classes"/>编译输出路径。
我把build.xml放在了WEB-INF目录下,输出路径就设置为该目录下的classes目录,待增强的字节码文件为classes目录下的com/derek/known/hbm/Knownquestions.class; 在命令行下切换到此目录,执行ant命令,即生成新的Knownquestions.class。
● 放弃懒加载,改为join的加载策略,或者使用hql:from Card t inner join fetch t.student r
3. 懒加载注解方式详解
● FetchType(@OneToOne、@OneToMany等注解里的fetch属性对应的类,本身没有注解)
JPA标准的通用加载策略注解属性。FetchType可选值意义与区别如下:
FetchType.LAZY:懒加载,在第一次访问关联对象的时候加载
FetchType.EAGER:立刻加载,在查询主对象的时候同时加载关联对象。
● @Fetch
hibernate定义了加载关联关系的获取策略。Fetch可选值意义与区别如下:
FetchMode.JOIN:始终立刻加载,使用外连(outer join)查询的同时加载关联对象,忽略FetchType.LAZY设定。
FetchMode.SELECT:支持懒加载(除非设定关联属性lazy=false),当访问每一个关联对象时加载该对象,会累计产生N+1条sql语句
FetchMode.SUBSELECT:支持懒加载(除非设定关联属性lazy=false),在访问第一个关联对象时加载所有的关联对象。会累计产生两条sql语句。
● @LazyToOne
hibernate定义了@ManyToOne 和@OneToOne 关联的延迟选项。LazyToOne可选值意义与区别如下:
LazyToOneOption.PROXY:基于代理的延迟加载。
LazyToOneOption.NO_PROXY:基于字节码增强的延迟加载 - 注意需要在构建期处理字节码增强。
LazyToOneOption.FALSE:非延迟加载的关联。
● @LazyCollection
hibernate定义了@ManyToMany和@OneToMany 关联的延迟选项。LazyCollection可选值意义与区别如下:
LazyCollectionOption.TRUE:集合具有延迟性,只有在访问的时候才加载。
LazyCollectionOption.EXTRA:集合具有延迟性,并且所有的操作都会尽量避免加载集合, 对于一个巨大的集合特别有用,因为这样的集合中的元素没有必要全部加载。
LazyCollectionOption.FALSE:非延迟加载的关联。
延迟和获取选项的等效注解
Annotations | Lazy | Fetch |
---|---|---|
@[One|Many]ToOne](fetch=FetchType.LAZY) | @LazyToOne(PROXY) | @Fetch(SELECT) |
@[One|Many]ToOne](fetch=FetchType.EAGER) | @LazyToOne(FALSE) | @Fetch(JOIN) |
@ManyTo[One|Many](fetch=FetchType.LAZY) | @LazyCollection(TRUE) | @Fetch(SELECT) |
@ManyTo[One|Many](fetch=FetchType.EAGER) | @LazyCollection(FALSE) | @Fetch(JOIN) |
Hibernate @OneToOne懒加载实现解决方案
在hibernate注解(三)中,我提高过一对一(@OneToOne)懒加载失效的问题。虽然给出了解决方法,但并没有给出完整的解决方案。今天我专门针对该问题进行讨论。至于懒加载失效的原因,在之前的文章中已经我已经叙述过了,就不再重复了,不明白的可以去看看。
数据库:myqsl
代码:主:Student,从:Card
表:
- DROP TABLE IF EXISTS `student`;
- CREATE TABLE `student` (
- `ID` int(11) NOT NULL,
- `NAME` varchar(50) NOT NULL,
- `CARD_ID` int(11) DEFAULT NULL,
- PRIMARY KEY (`ID`),
- KEY `PK_CARD_ID` (`CARD_ID`),
- CONSTRAINT `PK_CARD_ID` FOREIGN KEY (`CARD_ID`) REFERENCES `card` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- DROP TABLE IF EXISTS `card`;
- CREATE TABLE `card` (
- `ID` int(11) NOT NULL,
- `CODE` varchar(32) NOT NULL,
- PRIMARY KEY (`ID`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`ID` int(11) NOT NULL,
`NAME` varchar(50) NOT NULL,
`CARD_ID` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `PK_CARD_ID` (`CARD_ID`),
CONSTRAINT `PK_CARD_ID` FOREIGN KEY (`CARD_ID`) REFERENCES `card` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `card`;
CREATE TABLE `card` (
`ID` int(11) NOT NULL,
`CODE` varchar(32) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
代码:
- package com.po;
- import javax.persistence.CascadeType;
- import javax.persistence.Column;
- import javax.persistence.Entity;
- import javax.persistence.FetchType;
- import javax.persistence.GeneratedValue;
- import javax.persistence.GenerationType;
- import javax.persistence.Id;
- import javax.persistence.JoinColumn;
- import javax.persistence.OneToOne;
- import javax.persistence.Table;
- @Entity
- @Table(name = "Student")
- public class Student {
- private int id;
- private String name;
- private Card card;
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column(name = "ID", unique = true, nullable = false)
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- @Column(name = "NAME", nullable = false, length = 50)
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
- @JoinColumn(name = "CARD_ID")
- public Card getCard() {
- return card;
- }
- public void setCard(Card card) {
- this.card = card;
- }
- }
package com.po;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
@Table(name = "Student")
public class Student {
private int id;
private String name;
private Card card;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID", unique = true, nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name = "NAME", nullable = false, length = 50)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "CARD_ID")
public Card getCard() {
return card;
}
public void setCard(Card card) {
this.card = card;
}
}
- package com.po;
- import javax.persistence.CascadeType;
- import javax.persistence.Column;
- import javax.persistence.Entity;
- import javax.persistence.FetchType;
- import javax.persistence.GeneratedValue;
- import javax.persistence.GenerationType;
- import javax.persistence.Id;
- import javax.persistence.OneToOne;
- import javax.persistence.Table;
- @Entity
- @Table(name = "card")
- public class Card {
- private int id;
- private String code;
- private Student student;
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column(name = "ID", unique = true, nullable = false)
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- @Column(name = "CODE", length = 32, nullable = false)
- public String getCode() {
- return code;
- }
- public void setCode(String code) {
- this.code = code;
- }
- @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card")
- public Student getStudent() {
- return student;
- }
- public void setStudent(Student student) {
- this.student = student;
- }
- }
package com.po;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
@Table(name = "card")
public class Card {
private int id;
private String code;
private Student student;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID", unique = true, nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Column(name = "CODE", length = 32, nullable = false)
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card")
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
在card表增加一个student表的外键字段STUDENT_ID,并在Card类的@OneToOne下增加@JoinColumn(name = "STUDENT_ID"),去掉mappedBy = "card",即
- DROP TABLE IF EXISTS `card`;
- CREATE TABLE `card` (
- `ID` int(11) NOT NULL,
- `CODE` varchar(32) NOT NULL,
- `STUDENT_ID` int(11) DEFAULT NULL,
- PRIMARY KEY (`ID`),
- KEY `PK_STUDENT_ID` (`STUDENT_ID`),
- CONSTRAINT `PK_STUDENT_ID` FOREIGN KEY (`STUDENT_ID`) REFERENCES `student` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `card`;
CREATE TABLE `card` (
`ID` int(11) NOT NULL,
`CODE` varchar(32) NOT NULL,
`STUDENT_ID` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `PK_STUDENT_ID` (`STUDENT_ID`),
CONSTRAINT `PK_STUDENT_ID` FOREIGN KEY (`STUDENT_ID`) REFERENCES `student` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- public class Card {
- // ... 略
- @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
- @JoinColumn(name = "STUDENT_ID")
- public Student getStudent() {
- return student;
- }
- // ... 略
- }
public class Card {
// ... 略
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "STUDENT_ID")
public Student getStudent() {
return student;
}
// ... 略
}
优点:不改变Student与Card在代码中的对应关系(一对一)
缺点:需要同时维护Student和Card的两个外键。
改为主键关联。
- DROP TABLE IF EXISTS `student`;
- CREATE TABLE `student` (
- `ID` int(11) NOT NULL,
- `NAME` varchar(50) NOT NULL,
- PRIMARY KEY (`ID`),
- CONSTRAINT `PK_CARD_ID` FOREIGN KEY (`ID`) REFERENCES `card` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`ID` int(11) NOT NULL,
`NAME` varchar(50) NOT NULL,
PRIMARY KEY (`ID`),
CONSTRAINT `PK_CARD_ID` FOREIGN KEY (`ID`) REFERENCES `card` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- public class Student {
- // ... 略
- @Id
- @GenericGenerator(name = "PK_Card", strategy = "foreign", parameters = @Parameter(name = "property", value = "card"))
- @GeneratedValue(generator = "PK_Card")
- @Column(name = "ID", unique = true, nullable = false)
- public int getId() {
- return id;
- }
- // ... 略
- @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
- @PrimaryKeyJoinColumn
- public Card getCard() {
- return card;
- }
- // ... 略
- }
public class Student {
// ... 略
@Id
@GenericGenerator(name = "PK_Card", strategy = "foreign", parameters = @Parameter(name = "property", value = "card"))
@GeneratedValue(generator = "PK_Card")
@Column(name = "ID", unique = true, nullable = false)
public int getId() {
return id;
}
// ... 略
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
@PrimaryKeyJoinColumn
public Card getCard() {
return card;
}
// ... 略
}
- public class Card {
- // ... 略
- @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card", optional = false)
- public Student getStudent() {
- return student;
- }
- // ... 略
- }
public class Card {
// ... 略
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card", optional = false)
public Student getStudent() {
return student;
}
// ... 略
}
除了改变student表的主键、外键结构外,Student类和Card类也要做相应修改,尤其注意“optional”,要设置为false,否则无法实现懒加载。
优点:不改变Student与Card在代码中的对应关系(一对一)
缺点:改动较大,且使用主键关联具有局限性。
PS:主键关联的局限性
使用主键关联会影响数据存储结构,主键关联是一种强耦合,以上述为例:Card存在时,Student才能存在,Card消亡时,Student也随之消失。这是因为Student的主键依赖于Card主键,Student无法独立存在(就是说必须先有学生卡,才能有学生)。
将Card类中的OneToOne改为OneToMany(一对多)。
- public class Card {
- private Set<student> students;
- // ... 略
- @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card")
- public Set<Student> getStudents() {
- return students;
- }
- public void setStudents(Set<student> students) {
- this.students = students;
- }
- // ... 略
- }</student></student>
public class Card {
private Set<student> students;
// ... 略
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card")
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<student> students) {
this.students = students;
}
// ... 略
}</student></student>
优点:数据库不用修改
缺点:需要修改Student与Card在代码中的对应关系
方案四
放弃用注解的方式,改为Xml方式来实现hibernate模型设计,并在Card Xml的OneToOne标签中添加constrained属性,靠注解解决的办法已经没有了(instrument增强就算了吧,很麻烦)。
最后,我们来评估下以上方案的可行性。
方案一:从可读性来讲,是最容易理解的,但需要维护两个外键,如果程序控制不好的话,容易出问题,即关联错误。
方案二:主键关联虽然有些约束,但也取决于业务需求,比如订单和订单详情,采用主键关联也挺合适的,只是不适合相对灵活的对象关系。
方案三:改动在我看来是最小的了,牺牲了一定的可读性(关系从Card角度看变为了一对多),我个人比较喜欢该种方案,因此推荐。
方案四:如果不采用注解,而采用Xml的话,我是很推荐这种方案的,注解虽然优点多,也趋于主流,但最传统的Xml,功能还是最强大的。但如果你仅为了解决该问题,而将注解和Xml混合使用的话,我建议你还是放弃吧