hibernate注解(1-2-3)+Hibernate @OneToOne懒加载实现解决方案

    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之间的一对一的关联.一对一关联有三种情况:一是关联的实体都共享同样的主键,二是其中一个实体通过外键关联到另一个实体的主键(注意要模拟一对一关联必须在外键列上添加唯一约束).三是通过关联表来保存两个实体之间的连接关系(注意要模拟一对一关联必须在每一个外键上添加唯一约束).

首先,我们通过共享主键来进行一对一关联映射:

  1. @Entity  
  2. public class Body {  
  3.     @Id  
  4.     public Long getId() { return id; }  
  5.   
  6.     @OneToOne(cascade = CascadeType.ALL)  
  7.     @PrimaryKeyJoinColumn  
  8.     public Heart getHeart() {  
  9.         return heart;  
  10.     }  
  11.     ...  
  12. }  
  13.   
  14. @Entity  
  15. public class Heart {  
  16.     @Id  
  17.     public Long getId() { ...}  
  18. }  

@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定义了一对一关联.

下面这个例子使用外键列进行实体的关联.

  1. @Entity  
  2. public class Customer implements Serializable {  
  3.     @OneToOne(cascade = CascadeType.ALL)  
  4.     @JoinColumn(name="passport_fk")  
  5.     public Passport getPassport() {  
  6.         ...  
  7.     }  
  8.   
  9. @Entity  
  10. public class Passport implements Serializable {  
  11.     @OneToOne(mappedBy = "passport")  
  12.     public Customer getOwner() {  
  13.     ...  
  14. }  
@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.

第三种方式也许是最另类的(通过关联表).

  1. @Entity  
  2. public class Customer implements Serializable {  
  3.     @OneToOne(cascade = CascadeType.ALL)  
  4.     @JoinTable(name = "CustomerPassports",  
  5.         joinColumns = @JoinColumn(name="customer_fk"),  
  6.         inverseJoinColumns = @JoinColumn(name="passport_fk")  
  7.     )  
  8.     public Passport getPassport() {  
  9.         ...  
  10.     }  
  11.   
  12. @Entity  
  13. public class Passport implements Serializable {  
  14.     @OneToOne(mappedBy = "passport")  
  15.     public Customer getOwner() {  
  16.     ...  
  17. }  
@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注解来定义多对一关联:

  1. @Entity()  
  2. public class Flight implements Serializable {  
  3.     @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )  
  4.     @JoinColumn(name="COMP_ID")  
  5.     public Company getCompany() {  
  6.         return company;  
  7.     }  
  8.     ...  
  9. }  
@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的参数, 该参数定义了目标实体名.通常不需要定义该参数, 因为在大部分情况下默认值(表示关联关系的属性类型)就可以很好的满足要求了. 不过下面这种情况下这个参数就显得有意义了:使用接口作为返回值而不是常见的实体.

  1. @Entity()  
  2. public class Flight implements Serializable {  
  3.     @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class )  
  4.     @JoinColumn(name="COMP_ID")  
  5.     public Company getCompany() {  
  6.         return company;  
  7.     }  
  8.     ...  
  9. }  
  10.   
  11. public interface Company {  
  12.     ...  
  13.            
@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).

  1. @Entity()  
  2. public class Flight implements Serializable {  
  3.     @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )  
  4.     @JoinTable(name="Flight_Company",  
  5.         joinColumns = @JoinColumn(name="FLIGHT_ID"),  
  6.         inverseJoinColumns = @JoinColumn(name="COMP_ID")  
  7.     )  
  8.     public Company getCompany() {  
  9.         return company;  
  10.     }  
  11.     ...  
  12. }  
@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=... )

  1. @Entity  
  2. public class Troop {  
  3.     @OneToMany(mappedBy="troop")  
  4.     public Set<Soldier> getSoldiers() {  
  5.     ...  
  6. }  
  7.   
  8. @Entity  
  9. public class Soldier {  
  10.     @ManyToOne  
  11.     @JoinColumn(name="troop_fk")  
  12.     public Troop getTroop() {  
  13.     ...  
  14. }  
@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语句.

  1. @Entity  
  2. public class Troop {  
  3.     @OneToMany  
  4.     @JoinColumn(name="troop_fk"//we need to duplicate the physical information  
  5.     public Set<Soldier> getSoldiers() {  
  6.     ...  
  7. }  
  8.   
  9. @Entity  
  10. public class Soldier {  
  11.     @ManyToOne  
  12.     @JoinColumn(name="troop_fk", insertable=false, updatable=false)  
  13.     public Troop getTroop() {  
  14.     ...  
  15. }  
@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注解来描述这种单向关联关系.

  1. @Entity  
  2. public class Customer implements Serializable {  
  3.     @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)  
  4.     @JoinColumn(name="CUST_ID")  
  5.     public Set<Ticket> getTickets() {  
  6.     ...  
  7. }  
  8.   
  9. @Entity  
  10. public class Ticket implements Serializable {  
  11.     ... //no bidir  
  12. }  
@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注解来进行描述.

  1. @Entity  
  2. public class Trainer {  
  3.     @OneToMany  
  4.     @JoinTable(  
  5.             name="TrainedMonkeys",  
  6.             joinColumns = @JoinColumn( name="trainer_id"),  
  7.             inverseJoinColumns = @JoinColumn( name="monkey_id")  
  8.     )  
  9.     public Set<Monkey> getTrainedMonkeys() {  
  10.     ...  
  11. }  
  12.   
  13. @Entity  
  14. public class Monkey {  
  15.     ... //no bidir  
  16. }  
@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)表名. 指向主表的外键名:主表表名+下划线+主表主键列名 指向从表的外键名:主表所对应实体的属性名+下划线+从表主键列名 指向从表的外键定义为唯一约束,用来表示一对多的关联关系.

  1. @Entity  
  2. public class Trainer {  
  3.     @OneToMany  
  4.     public Set<Tiger> getTrainedTigers() {  
  5.     ...  
  6. }  
  7.   
  8. @Entity  
  9. public class Tiger {  
  10.     ... //no bidir  
  11. }  
@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(在对关联表进行更新操作时这一端将被忽略):

  1. @Entity  
  2. public class Employer implements Serializable {  
  3.     @ManyToMany(  
  4.         targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,  
  5.         cascade={CascadeType.PERSIST, CascadeType.MERGE}  
  6.     )  
  7.     @JoinTable(  
  8.         name="EMPLOYER_EMPLOYEE",  
  9.         joinColumns=@JoinColumn(name="EMPER_ID"),  
  10.         inverseJoinColumns=@JoinColumn(name="EMPEE_ID")  
  11.     )  
  12.     public Collection getEmployees() {  
  13.         return employees;  
  14.     }  
  15.     ...  
  16. }  
  17.   
  18. @Entity  
  19. public class Employee implements Serializable {  
  20.     @ManyToMany(  
  21.         cascade = {CascadeType.PERSIST, CascadeType.MERGE},  
  22.         mappedBy = "employees",  
  23.         targetEntity = Employer.class  
  24.     )  
  25.     public Collection getEmployers() {  
  26.         return employers;  
  27.     }  
  28. }  
@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根据以下规则生成相应的值. 关联表名:主表表名+_下划线+从表表名, 关联到主表的外键名:主表名+_下划线+主表中的主键列名. 关联到从表的外键名:主表中用于关联的属性名+_下划线+从表的主键列名. 以上规则对于双向一对多关联同样有效.

  1. @Entity  
  2. public class Store {  
  3.     @ManyToMany(cascade = CascadeType.PERSIST)  
  4.     public Set<City> getImplantedIn() {  
  5.         ...  
  6.     }  
  7. }  
  8.   
  9. @Entity  
  10. public class City {  
  11.     ... //no bidirectional relationship  
  12. }  
@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根据以下规则生成相应的值 关联表名: :主表表名+_下划线+从表表名, 关联到主表的外键名:从表用于关联的属性名+_下划线+主表中的主键列名. 关联到从表的外键名:主表用于关联的属性名+_下划线+从表的主键列名. 以上规则对于双向一对多关联同样有效.

  1. @Entity  
  2. public class Store {  
  3.     @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})  
  4.     public Set<Customer> getCustomers() {  
  5.         ...  
  6.     }  
  7. }  
  8.   
  9. @Entity  
  10. public class Customer {  
  11.     @ManyToMany(mappedBy="customers")  
  12.     public Set<Store> getStores() {  
  13.         ...  
  14.     }  
  15. }  
@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对此提供了支持

  1. @Entity public class City {  
  2.     @OneToMany(mappedBy="city")  
  3.     @OrderBy("streetName")  
  4.     public List<Street> getStreets() {  
  5.         return streets;  
  6.     }  
  7. ...  
  8. }  
  9.   
  10. @Entity public class Street {  
  11.     public String getStreetName() {  
  12.         return streetName;  
  13.     }  
  14.   
  15.     @ManyToOne  
  16.     public City getCity() {  
  17.         return city;  
  18.     }  
  19.     ...  
  20. }  
  21.   
  22.   
  23. @Entity  
  24. public class Software {  
  25.     @OneToMany(mappedBy="software")  
  26.     @MapKey(name="codeName")  
  27.     public Map<String, Version> getVersions() {  
  28.         return versions;  
  29.     }  
  30. ...  
  31. }  
  32.   
  33. @Entity  
  34. @Table(name="tbl_version")  
  35. public class Version {  
  36.     public String getCodeName() {...}  
  37.   
  38.     @ManyToOne  
  39.     public Software getSoftware() { ... }  
  40. ...  
  41. }  
@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()方法.

  1. @Entity  
  2. public class RegionalArticle implements Serializable {  
  3.   
  4.     @Id  
  5.     public RegionalArticlePk getPk() { ... }  
  6. }  
  7.   
  8. @Embeddable  
  9. public class RegionalArticlePk implements Serializable { ... }  
@Entity
public class RegionalArticle implements Serializable {

    @Id
    public RegionalArticlePk getPk() { ... }
}

@Embeddable
public class RegionalArticlePk implements Serializable { ... }

或者

  1. @Entity  
  2. public class RegionalArticle implements Serializable {  
  3.   
  4.     @EmbeddedId  
  5.     public RegionalArticlePk getPk() { ... }  
  6. }  
  7.   
  8. 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认为你使用的列顺序和主键声明的顺序一致.

  1. @Entity  
  2. public class Parent implements Serializable {  
  3.     @Id  
  4.     public ParentPk id;  
  5.     public int age;  
  6.   
  7.     @OneToMany(cascade=CascadeType.ALL)  
  8.     @JoinColumns ({  
  9.         @JoinColumn(name="parentCivility", referencedColumnName = "isMale"),  
  10.         @JoinColumn(name="parentLastName", referencedColumnName = "lastName"),  
  11.         @JoinColumn(name="parentFirstName", referencedColumnName = "firstName")  
  12.     })  
  13.     public Set<Child> children; //unidirectional  
  14.     ...  
  15. }  
  16.   
  17. @Entity  
  18. public class Child implements Serializable {  
  19.     @Id @GeneratedValue  
  20.     public Integer id;  
  21.   
  22.     @ManyToOne  
  23.     @JoinColumns ({  
  24.         @JoinColumn(name="parentCivility", referencedColumnName = "isMale"),  
  25.         @JoinColumn(name="parentLastName", referencedColumnName = "lastName"),  
  26.         @JoinColumn(name="parentFirstName", referencedColumnName = "firstName")  
  27.     })  
  28.     public Parent parent; //unidirectional  
  29. }  
  30.   
  31. @Embeddable  
  32. public class ParentPk implements Serializable {  
  33.     String firstName;  
  34.     String lastName;  
  35.     ...  
  36. }  
@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表:

  1. id  int  not null  
  2. name  varchar(50)  not null  
  3. card_id  int  not null  

id  int  not null
name  varchar(50)  not null
card_id  int  not null

Card表:

  1. id  int  not null  
  2. card_no  varchar()  not null  
id  int  not null
card_no  varchar()  not null

Student类

  1. public class Student {  
  2.   
  3.     private int id;  
  4.     private String name;  
  5.     private Card card;  
  6.   
  7.     ....  
  8.   
  9.     @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)  
  10.     @JoinColumn(name = "card_id")  
  11.     public Card getCard() {  
  12.         return card;  
  13.     }  
  14.   
  15.     public void setCard(Card card) {  
  16.         this.card = card;  
  17.     }  
  18. }  
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类

  1. public class Card {  
  2.   
  3.     private int id;  
  4.     private String cardNo;  
  5.     private Student student;  
  6.   
  7.     ....  
  8.   
  9.     @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "card")  
  10.     public Student getStudent() {  
  11.         return student;  
  12.     }  
  13.   
  14.     public void setStudent(Student student) {  
  15.         this.student = student;  
  16.     }  
  17. }  
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脚本如下:

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <project name="hibernatelazy" default="instrument" basedir=".">  
  3.        <property name="lib.dir" value="./lib"/>  
  4.        <property name="classes.dir" value="./classes"/>  
  5.   
  6.        <path id="lib.class.path">  
  7.              <fileset dir="${lib.dir}">  
  8.                     <include name="**/*.jar"/>  
  9.               </fileset>  
  10.         </path>  
  11.         <target name="instrument">  
  12.             <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">  
  13.                      <classpath path="${classes.dir}"/>  
  14.                      <classpath refid="lib.class.path"/>  
  15.              </taskdef>  
  16.              <instrument verbose="true">  
  17.                       <fileset dir="${classes.dir}/com/derek/known/hbm">  
  18.                             <include name="Knownquestions.class"/>  
  19.                       </fileset>  
  20.               </instrument>  
  21.          </target>  
  22. </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

表:

  1. DROP TABLE IF EXISTS `student`;  
  2. CREATE TABLE `student` (  
  3.   `ID` int(11) NOT NULL,  
  4.   `NAMEvarchar(50) NOT NULL,  
  5.   `CARD_ID` int(11) DEFAULT NULL,  
  6.   PRIMARY KEY (`ID`),  
  7.   KEY `PK_CARD_ID` (`CARD_ID`),  
  8.   CONSTRAINT `PK_CARD_ID` FOREIGN KEY (`CARD_ID`) REFERENCES `card` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION  
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;  
  10.   
  11. DROP TABLE IF EXISTS `card`;  
  12. CREATE TABLE `card` (  
  13.   `ID` int(11) NOT NULL,  
  14.   `CODE` varchar(32) NOT NULL,  
  15.   PRIMARY KEY (`ID`)  
  16. ) 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;

代码:

  1. package com.po;  
  2.   
  3. import javax.persistence.CascadeType;  
  4. import javax.persistence.Column;  
  5. import javax.persistence.Entity;  
  6. import javax.persistence.FetchType;  
  7. import javax.persistence.GeneratedValue;  
  8. import javax.persistence.GenerationType;  
  9. import javax.persistence.Id;  
  10. import javax.persistence.JoinColumn;  
  11. import javax.persistence.OneToOne;  
  12. import javax.persistence.Table;  
  13.   
  14. @Entity  
  15. @Table(name = "Student")  
  16. public class Student {  
  17.   
  18.     private int id;  
  19.     private String name;  
  20.     private Card card;  
  21.   
  22.     @Id  
  23.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  24.     @Column(name = "ID", unique = true, nullable = false)  
  25.     public int getId() {  
  26.         return id;  
  27.     }  
  28.   
  29.     public void setId(int id) {  
  30.         this.id = id;  
  31.     }  
  32.   
  33.     @Column(name = "NAME", nullable = false, length = 50)  
  34.     public String getName() {  
  35.         return name;  
  36.     }  
  37.   
  38.     public void setName(String name) {  
  39.         this.name = name;  
  40.     }  
  41.   
  42.     @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)  
  43.     @JoinColumn(name = "CARD_ID")  
  44.     public Card getCard() {  
  45.         return card;  
  46.     }  
  47.   
  48.     public void setCard(Card card) {  
  49.         this.card = card;  
  50.     }  
  51. }  
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;
	}
}
  1. package com.po;  
  2.   
  3. import javax.persistence.CascadeType;  
  4. import javax.persistence.Column;  
  5. import javax.persistence.Entity;  
  6. import javax.persistence.FetchType;  
  7. import javax.persistence.GeneratedValue;  
  8. import javax.persistence.GenerationType;  
  9. import javax.persistence.Id;  
  10. import javax.persistence.OneToOne;  
  11. import javax.persistence.Table;  
  12.   
  13. @Entity  
  14. @Table(name = "card")  
  15. public class Card {  
  16.   
  17.     private int id;  
  18.     private String code;  
  19.     private Student student;  
  20.   
  21.     @Id  
  22.     @GeneratedValue(strategy = GenerationType.IDENTITY)  
  23.     @Column(name = "ID", unique = true, nullable = false)  
  24.     public int getId() {  
  25.         return id;  
  26.     }  
  27.   
  28.     public void setId(int id) {  
  29.         this.id = id;  
  30.     }  
  31.   
  32.     @Column(name = "CODE", length = 32, nullable = false)  
  33.     public String getCode() {  
  34.         return code;  
  35.     }  
  36.   
  37.     public void setCode(String code) {  
  38.         this.code = code;  
  39.     }  
  40.   
  41.     @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card")  
  42.     public Student getStudent() {  
  43.         return student;  
  44.     }  
  45.   
  46.     public void setStudent(Student student) {  
  47.         this.student = student;  
  48.     }  
  49. }  
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",即

  1. DROP TABLE IF EXISTS `card`;  
  2. CREATE TABLE `card` (  
  3.   `ID` int(11) NOT NULL,  
  4.   `CODE` varchar(32) NOT NULL,  
  5.   `STUDENT_ID` int(11) DEFAULT NULL,  
  6.   PRIMARY KEY (`ID`),  
  7.   KEY `PK_STUDENT_ID` (`STUDENT_ID`),  
  8.   CONSTRAINT `PK_STUDENT_ID` FOREIGN KEY (`STUDENT_ID`) REFERENCES `student` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION  
  9. ) 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;
  1. public class Card {  
  2.   
  3.     // ... 略  
  4.   
  5.     @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)  
  6.     @JoinColumn(name = "STUDENT_ID")  
  7.     public Student getStudent() {  
  8.         return student;  
  9.     }  
  10.   
  11.     // ... 略  
  12. }  
public class Card {

	// ... 略

	@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
	@JoinColumn(name = "STUDENT_ID")
	public Student getStudent() {
		return student;
	}

	// ... 略
}

优点:不改变Student与Card在代码中的对应关系(一对一)

缺点:需要同时维护Student和Card的两个外键。


方案二

改为主键关联。

  1. DROP TABLE IF EXISTS `student`;  
  2. CREATE TABLE `student` (  
  3.   `ID` int(11) NOT NULL,  
  4.   `NAMEvarchar(50) NOT NULL,  
  5.   PRIMARY KEY (`ID`),  
  6.   CONSTRAINT `PK_CARD_ID` FOREIGN KEY (`ID`) REFERENCES `card` (`ID`) ON DELETE NO ACTION ON UPDATE NO ACTION  
  7. ) 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;
  1. public class Student {  
  2.   
  3.     // ... 略  
  4.   
  5.     @Id  
  6.     @GenericGenerator(name = "PK_Card", strategy = "foreign", parameters = @Parameter(name = "property", value = "card"))  
  7.     @GeneratedValue(generator = "PK_Card")  
  8.     @Column(name = "ID", unique = true, nullable = false)  
  9.     public int getId() {  
  10.         return id;  
  11.     }  
  12.   
  13.     // ... 略  
  14.   
  15.     @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)  
  16.     @PrimaryKeyJoinColumn  
  17.     public Card getCard() {  
  18.         return card;  
  19.     }  
  20.   
  21.     // ... 略  
  22. }  
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;
	}

	// ... 略
}
  1. public class Card {  
  2.   
  3.     // ... 略  
  4.   
  5.     @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card", optional = false)  
  6.     public Student getStudent() {  
  7.         return student;  
  8.     }  
  9.   
  10.     // ... 略  
  11. }  
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(一对多)。

  1. public class Card {  
  2.   
  3.     private Set<student> students;  
  4.   
  5.     // ... 略  
  6.   
  7.     @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card")  
  8.     public Set<Student> getStudents() {  
  9.         return students;  
  10.     }  
  11.   
  12.     public void setStudents(Set<student> students) {  
  13.         this.students = students;  
  14.     }  
  15.   
  16.     // ... 略  
  17. }</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混合使用的话,我建议你还是放弃吧





  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值