【大话Hibernate】Hibernate两种实体关系映射详解

 

实体类与数据库之间存在某种映射关系,Hibernate依据这种映射关系完成数据的存取,因此映射关系的配置在Hibernate中是最关键的。Hibernate支持xml配置文件与@注解配置两种方式。xml配置文件是最基础的配置,而@注解是Java的官方JPA(Java Persistence API)提供的。本章分别使用@注解与xml讲解Hibernate的映射配置。


一、实体类映射:

 

Java的角度讲,实体类就是普通的Java封装类(有人称为POJO有人称为VO)。仅从实体类中的代码信息,Hibernate并不能得知该实体类对应哪个数据表,因此还需要以某种方式配置一下。常用的方式有*.hbm.xml文件配置与@注解配置两种。

hbm.xml文件就是普通的xml文件,hbmHibernateMapping的缩写,这样从文件名上就能判断该文件为Hibernate实体类配置文件。在JPA出现之前,Hibernate都使用hbm.xml文件配置。JPA出现后,推荐使用JPA@注解配置,因为对于所有的ORM框架,@注解都是通用的。

 

1、@注解配置:

实体类一般有ID、普通属性、集合属性等,分别对应数据库的主键、普通列、外键。在@注解配置中,实体类用@Entity注解,用@Table指定对应的数据表,用@Id配置主键,用@Column配置普通属性,用@OneToMany、@ManyToOne、@OneToOne、@ManyToMany配置是实体间的关系。

 

2XML配置:

多个实体类可以配置在一个XML文件中。Hibernate推荐用一个同名的XML文件配置一个实体类,便于阅读和维护。XML文件一般以“*.hbm.xml”结尾,便于辨认,也可以直接使用“*.xml”结尾。

实体类还需要配置到hibernate.cfg.xml中,以便hibernate初始化实体类与数据库表的映射关系。如果只配置了映射关系,而没有配置到hibernate.cfg.xml中,hibernate仍然不会知道哪些类是实体类,因为hibernate无法通过遍历所有的类来决定哪些是实体类。如果实体类是用@注解配置的,需要用配置,而如果是用XML文件配置的,则需要使用配置XML配置文件。

 

 

二、主键映射:


实体类最好有主键列,并有对应的gettersetter方法,这是hibernate推荐的。逐渐尽量使用可以为null值的类型,例如IntegerLongString等,而不要使用intlong等。因为如果主键为null,则表示该实体类还没保存到数据库,是一个临时状态(Transient),而intlong等原始类型则不具备该功能。


1@注解配置主键

       Hibernate中用@Id声明该列为主键列,同时用@Column声明该列的列名。当列名与属性名相同时,@Column配置可省略。@GeneratedValue用于指定主键的生成策略。Hibernate支持多种逐渐生成规则,例如自增长、由某个表决定、由Sequence决定等等。如果不配置

 

@GeneratedValue,则必须手动设置ID值。
@Id
@Column(name = "id")
// 设置主键类型, auto表示主键是自增长类型
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;


 

2.  XML文件中配置主键


如果使用XML配置,主键用配置,name指定实体类的主键属性,column指定数据表中的主键列名。使用嵌套的配置主键生成策略,native表示使用数据库自己的策略,在MySQL中就是自增长类型,如果不用自动增长类型,则可以用assigned,例如:

 





 

 主键生成规则

上面说了主键的配置,在配置主键过程中,配置了主键是否是自动生成的。@Id配置主键的同时,也要用@GeneratedValue配置主键生成规则。主键生成规则也成为主键生成策略,负责维护新实体的主键值。用的最多的策略是自增长策略。Hibernate还支持其他的多种主键生成规则。这些生成规则有些是数据库提供的,有些是Hibernate提供的。

 

1.  使用@注解配置主键生成规则

到目前为止,@注解只支持四种逐渐生成策略:GenerationType.AUTOGenerationType.TABLEGenerationType.SEQUENCEGenerationType.IDENTITY,意义分别为:

qGenerationType.AUTO:自动方式,根据底层数据库自动选择。如果为MySQL等支持自增长类型的数据库,则为自增长类型(auto_increment)。

 

qGenerationType.TABLE:使用指定的表来决定主键的取值,一般结合@TableGenerator使用,示例代码如下:

 

@Id
@TableGenerator(name = "tb_cat_gen", allocationSize= 1)
@GeneratedValue(strategy = GenerationType.TABLE, generator= "tb_cat_gen")
private Integer id;


 

qGenerationType.SEQUENCE:使用Sequence来决定主键的取值,适合OracleDB2PostgreSQLSAPDB等支持Sequence的数据库,一般结合@SequenceGenerator使用。注意某些数据库如Oracle等没有自增长类型,只能使用Sequence,示例代码如下:

 

@Id
@SequenceGenerator(name = "seq_cat", allocationSize= 25)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator= "seq_cat")
@Column(name = "id")
private Integer id;


 

qGenerationType.IDENTITY:支持DB2MySQLMSSQLServerSybaseHypersonicSQL数据库的identit类型主键。

 

2.  使用XML文件配置主键生成规则

 

XML配置中支持的主键生成规则,比使用@注解的配置的主键生成规则要多,XML配置中支持的主键生成规则有以下几种:

qnative:取决于数据库,相当于GenerationType.AUTO

 

qidentity:使用identity类型,相当于GenerationType.IDENTITY

 

qsequence:使用sequence,相当于GenerationType.SEQUENCE。需要指定sequence的名称,示例代码如下:

 


    
         id_sequence
    


 

qincrement:自增长类型,由Hibernate而不是数据库维护,因此即使Oracle等不支持自增长类型的数据库也可以使用。

 

qhilohi/low算法,使用指定的表给主键赋值,相当于GenerationType.TABLE。需要指定表名、列名等,实例代码如下:

 


   
         users
         id
         200
   


 

qseqhilo:基于sequencehilo算法,例如:

 


   
        hi_value
           200
    


 

quuid:使用128位的UUID算法计算一个唯一的值,会使用IP地址及相关的计算机硬件信息。计算结果为32位的16进制数,对应的主键类型必须为String

 

qguid:使用MySQL或者MSSQLServer等数据库提供的GUID值。

 

qassigned:默认值,不使用任何策略,在保存进数据库之前必须使用setter方法赋值。

 

qselect:使用数据库触发器赋值。

 

qforeign:使用外键赋值,在一对一实体关系时,可保证关系双方的Id保持一致。

MySQL数据库与Hibernate都提供自增长策略,但是原理是不太一样的。如果采用MySQL的自增长,插入数据时Hibernate生成的SQL语句中将不包含id主键列数据。该主键的当前值、下一个值由数据库自己维护。如果使用Hibernate的自增长,插入数据时Hibernate生成的SQL语句将包含id主键列,并由Hibernate维护该主键的当前值以及下一个值。

对于普通的应用来说,数据库自增长与Hibernate自增长在使用上没有区别。但是如果某数据库同时被两个Hibernate程序使用,那么此时使用Hibernate自增长将会出现错误。例如如果当前主键值为101,那么Hibernate会认为下个主键值为102,两个Hibernate程序插入数据时都会将主键值设为101,这时会因为主键冲突而导致其中一个写数据失败。

 

 

      三、普通属性映射:


       普通属性是指除了主键外的、java基本类型的属性,例如Integer(int)Long(long)Short(short)Boolean(boolean)Double(double)Float(float)String(string)Date等类型属性。注意Integer类型与int类型是不同的,Integer默认为null,在数据库中也表现为null,而int默认为0,在数据库中也表现为0.

     

  1@配置普通属性

普通属性使用@Column@Basic配置。二者都可以省略。如果省略,则全部按照默认的规则配置,@Column@Basic的用法如下:

 

@Column中可指定nullable(是否允许为null)、unique(是否唯一)、insertable(是否允许插入)、updatable(是否允许更新)、length(列长度)、columnDefinition(列类型)、scale(整数长度)、precision(小数点精度)等。这些属性用于生成DDL建表语句。如果属性对应的列名与属性名一致,@Column可以省略。

 

q@Basic可为普通属性配置加载方式,默认为即时加载。如果列数据比较大,例如大文本类型或者LOB类型,可配置为延迟加载。optional配置该列是否可为null。如果为true,表示该属性是可选的,可以为null,否则不可以为null

@Column@Basic使用的代码示例如下:

 

@Column(name = "usersName", nullable = true, columnDefinition ="varchar", insertable= true, length = 255, unique = true, updatable= true, precision = 2, scale = 4)
@Basic(fetch = FetchType.LAZY, optional=true)
private String usersName;


 

2 、使用XML文件配置普通属性映射

XML中使用标签配置普通属性。type属性指定列类型,相当于@Column中的columnDefinition。例如,如果设置type=text”可以为String类型属性设置为大文本类型列。不同于@注解中的@Column,如果属性名与列名一致,column属性可省略,xml配置中的必须配置,否则视为不参与持久化的列。配置为:

 




 

在使用@注解配置中,如果没有对普通属性进行配置,则默认该属性名与数据表列名相同;而xml文件配置中,如果对普通属性没有配置,则认为该属性没有对应的数据库列,不参与持久化。二者是截然不同的。

 

 

四、日期属性配置:


       日期属性也属于普通属性,普通属性的配置规则也适用于日期属性。日期属性又包括只有日期没有时间(java.sql.Date)、没有日期只有时间(java.sql.Time)。既有日期又有时间(java.sql.Timestamp)等3种情况,因此要多一些配置。由于java.sql.Datejava.sql.Timejava.sql.Timestamp都是java.util.Date的子类,所以日期属性直接使用父类java.util.Date就可以了,hibernate会根据日期属性决定该类型是java.sql.Datejava.sql.Time还是java.sql.Timestamp

       1、使用@注解配置普通属性映射

       日期属性也是普通的属性,需要用@Basic声明加载方式、@Column等指定列名,二者都可省略。另外,如果日期属性是java.util.Date类型的,必须要用@Temporal配置日期类型,取值可以为DateTime或者Timestemp。否则Hibernate将无法区分该类型是到底是java.sql.Date(只有年月日等日期信息)类型还是java.sql.Time(只有时分秒等时间信息)类型、还是java.sql.TimeStamp(既有日期信息、又有时间信息)类型。例如:

 

@Temporal(TemporalType.TIMESTAMP)// 日期类型为DATE, TIME或者TIMESTEMP。
@Column(name = " birthday")
private java.util.Date birthday;


在配置日期属性时,如果属性类型是java.util.Date类型,需要用@Temporal声明日期类型。但是如果是java.sql.Timejava.sql.Date或者java.sql.TimeStamp类型的,类型本身就已经很明确了,不再需要@Temporal声明了。

 

2 、使用XML文件配置日期属性映射

在配置日期类型属性时,type属性中指定日期类型,取值可以为datetimetimestamp等简写方式,也可以为java.sql.Datejava.sql.Timejava.sql.Timestamp等全写方式。

同样的道理,如果Java中属性类型为java.util.Date类型,必须指定是java.sql.Date(只有年月日等日期信息)类型还是java.sql.Time(只有时分秒等时间信息)类型、还是java.sql.TimeStamp(既有日期信息、又有时间信息)类型。示例代码如下:

 



五、临时属性映射:


       实体类可能有一些临时属性,在JPA中被称为Transient属性。这些属性用于方便计算等其他用途,而不是保存数据到数据库中。这些属性必须被标记为Transient,以便hibernate把他们区别对待。否则hibernate会试图往数据库写该属性,可能会因对应的列不存在而抛出异常。

       1、使用@注解配置临时属性映射

Java标注中,临时属性必须使用@Transient标注,既可以配置在临时属性上,也可以配置在对应的gettersetter方法上。例如:

 

@Transient
public int getCount() {// 临时属性,用于计算总记录数
return name == null ? 0 :count;
}


如果只有形如gettersetter的方法,但是没有对应的属性,Hibernate仍然会认为该属性存在。因此也需要用@Transient标注。

  

2 、使用XML文件配置临时属性映射


XML配置中,所有没有配置到XML文件中的属性都被视为临时属性。如果某属性漏配置了,该属性值将不被保存到数据库中。

 

 

       六.版本属性配置:


       Hibernate中有一种特殊的属性:版本(Version)属性。版本属性不参与业务逻辑,只用来保证不会有两个线程同时对该数据进行写操作。版本属性是乐观锁的一种实现方法。乐观锁是相对于悲观锁而言的。


乐观锁与悲观锁:悲观锁与乐观锁都是保证数据准确性的机制。


为保证数据的准确性,程序必须保证在一个线程修改数据的时候,该数据没有被其他线程修改。在传统的数据库编程中,程序修改数据时先锁定该数据行,使其他程序无法修改该行数据,修改完毕后释放数据锁,以此保证数据准确性。由于该机制需要锁定数据行,被锁定的数据只能被一个线程使用,因此被称为悲观锁。

乐观锁使用完全不同的方式。乐观锁通过Version列保存当前数据的版本,如果程序修改了数据,就将版本加1。反过来,如果版本列有了变化,说明该数据被修改过了。程序保存数据时会检查数据的Version列。如果Version列已经发生了变化,程序会重新读取、修改并保存数据。由于该机制不需要锁定数据行,允许多条线程同时访问同一条数据,因此被称为乐观锁。乐观锁的效率要高于悲观锁,因袭从现代编程中更倾向于乐观锁。

在现代的web编程中,开发者已经不需要关心悲观锁、乐观锁的实现细节,只需要配置一下即可,框架底层会自动实现。


1、悲观锁的使用


Hibernate的悲观锁是使用SQL语句或者HQL语句实现的,下面是一个典型的倚赖数据库的悲观锁调用:


select * from users wherename=”Jack” for update


这条 sql语句锁定了users表中所有符合检索条件(name=Jack”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。Hibernate的悲观锁,也是基于数据库的锁机制实现。


下面的代码实现了对查询记录的加锁:

String hql ="fromUsers where name='Jack';

Query query = session.createQuery(hql);

query.setLockMode("users",LockMode.UPGRADE); //users表进行加锁

List list = query.list();// 执行查询,获取数据

query.setLockMode 对查询语句中,特定数据库表的记录进行加锁(这里是users表),这里也就是对返回的所有 users记录进行加锁。

观察运行期 Hibernate生成的SQL语句:

select users0_.id asid, users0_.name as name, users0_.group_id

as group_id, users0_.user_type as user_type, users0_.sex as sex

from users users0_ where users0_.name='Jack' ) for update

这里 Hibernate通过使用数据库的for update子句实现了悲观锁机制。


2、乐观锁的使用:


Hibernate支持乐观锁,保存数据时Hibernate会自动完成检查Version列、修改数据、更新Version列等工作。Hibernate隐藏了所有的Version操作细节,只需要指定实体类的Version列即可。实体类中可用@Version配置版本属性。版本列一般为数字类型属性。例如:


@Version
private int version;
XML中使用配置乐观锁,name属性配置版本列。注意版本列要配置在主键后面、普通属性前面。例如:

XML配置版本属性要比@配置灵活,版本属性既可以为int、long等数据类型,也可以为Timestamp时间戳等类型,配置时用type配置类型,例如:

或者直接用配置日期版本,与上面的配置是等价的:


你可能感兴趣的:(框架,大话Hibernate)