实体类与数据库之间存在某种映射关系,Hibernate依据这种映射关系完成数据的存取,因此映射关系的配置在Hibernate中是最关键的。Hibernate支持xml配置文件与@注解配置两种方式。xml配置文件是最基础的配置,而@注解是Java的官方JPA(Java Persistence API)提供的。本章分别使用@注解与xml讲解Hibernate的映射配置。
一、实体类映射:
从Java的角度讲,实体类就是普通的Java封装类(有人称为POJO有人称为VO)。仅从实体类中的代码信息,Hibernate并不能得知该实体类对应哪个数据表,因此还需要以某种方式配置一下。常用的方式有*.hbm.xml文件配置与@注解配置两种。
hbm.xml文件就是普通的xml文件,hbm为HibernateMapping的缩写,这样从文件名上就能判断该文件为Hibernate实体类配置文件。在JPA出现之前,Hibernate都使用hbm.xml文件配置。JPA出现后,推荐使用JPA的@注解配置,因为对于所有的ORM框架,@注解都是通用的。
1、@注解配置:
实体类一般有ID、普通属性、集合属性等,分别对应数据库的主键、普通列、外键。在@注解配置中,实体类用@Entity注解,用@Table指定对应的数据表,用@Id配置主键,用@Column配置普通属性,用@OneToMany、@ManyToOne、@OneToOne、@ManyToMany配置是实体间的关系。
2、XML配置:
多个实体类可以配置在一个XML文件中。Hibernate推荐用一个同名的XML文件配置一个实体类,便于阅读和维护。XML文件一般以“*.hbm.xml”结尾,便于辨认,也可以直接使用“*.xml”结尾。
实体类还需要配置到hibernate.cfg.xml中,以便hibernate初始化实体类与数据库表的映射关系。如果只配置了映射关系,而没有配置到hibernate.cfg.xml中,hibernate仍然不会知道哪些类是实体类,因为hibernate无法通过遍历所有的类来决定哪些是实体类。如果实体类是用@注解配置的,需要用
二、主键映射:
实体类最好有主键列,并有对应的getter和setter方法,这是hibernate推荐的。逐渐尽量使用可以为null值的类型,例如Integer、Long、String等,而不要使用int、long等。因为如果主键为null,则表示该实体类还没保存到数据库,是一个临时状态(Transient),而int、long等原始类型则不具备该功能。
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配置,主键用
上面说了主键的配置,在配置主键过程中,配置了主键是否是自动生成的。@Id配置主键的同时,也要用@GeneratedValue配置主键生成规则。主键生成规则也成为主键生成策略,负责维护新实体的主键值。用的最多的策略是自增长策略。Hibernate还支持其他的多种主键生成规则。这些生成规则有些是数据库提供的,有些是Hibernate提供的。
1. 使用@注解配置主键生成规则
到目前为止,@注解只支持四种逐渐生成策略:GenerationType.AUTO、GenerationType.TABLE、GenerationType.SEQUENCE、GenerationType.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来决定主键的取值,适合Oracle、DB2、PostgreSQL、SAPDB等支持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:支持DB2、MySQL、MSSQLServer、Sybase与HypersonicSQL数据库的identit类型主键。
2. 使用XML文件配置主键生成规则
XML配置中支持的主键生成规则,比使用@注解的配置的主键生成规则要多,XML配置中支持的主键生成规则有以下几种:
qnative:取决于数据库,相当于GenerationType.AUTO。
qidentity:使用identity类型,相当于GenerationType.IDENTITY。
qsequence:使用sequence,相当于GenerationType.SEQUENCE。需要指定sequence的名称,示例代码如下:
id_sequence
qincrement:自增长类型,由Hibernate而不是数据库维护,因此即使Oracle等不支持自增长类型的数据库也可以使用。
qhilo:hi/low算法,使用指定的表给主键赋值,相当于GenerationType.TABLE。需要指定表名、列名等,实例代码如下:
users
id
200
qseqhilo:基于sequence的hilo算法,例如:
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;
XML中使用
在使用@注解配置中,如果没有对普通属性进行配置,则默认该属性名与数据表列名相同;而xml文件配置中,如果对普通属性没有配置,则认为该属性没有对应的数据库列,不参与持久化。二者是截然不同的。
四、日期属性配置:
日期属性也属于普通属性,普通属性的配置规则也适用于日期属性。日期属性又包括只有日期没有时间(java.sql.Date)、没有日期只有时间(java.sql.Time)。既有日期又有时间(java.sql.Timestamp)等3种情况,因此要多一些配置。由于java.sql.Date、java.sql.Time、java.sql.Timestamp都是java.util.Date的子类,所以日期属性直接使用父类java.util.Date就可以了,hibernate会根据日期属性决定该类型是java.sql.Date、java.sql.Time还是java.sql.Timestamp。
日期属性也是普通的属性,需要用@Basic声明加载方式、@Column等指定列名,二者都可省略。另外,如果日期属性是java.util.Date类型的,必须要用@Temporal配置日期类型,取值可以为Date、Time或者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.Time、java.sql.Date或者java.sql.TimeStamp类型的,类型本身就已经很明确了,不再需要@Temporal声明了。
在配置日期类型属性时,type属性中指定日期类型,取值可以为date、time、timestamp等简写方式,也可以为java.sql.Date、java.sql.Time、java.sql.Timestamp等全写方式。
同样的道理,如果Java中属性类型为java.util.Date类型,必须指定是java.sql.Date(只有年月日等日期信息)类型还是java.sql.Time(只有时分秒等时间信息)类型、还是java.sql.TimeStamp(既有日期信息、又有时间信息)类型。示例代码如下:
五、临时属性映射:
实体类可能有一些临时属性,在JPA中被称为Transient属性。这些属性用于方便计算等其他用途,而不是保存数据到数据库中。这些属性必须被标记为Transient,以便hibernate把他们区别对待。否则hibernate会试图往数据库写该属性,可能会因对应的列不存在而抛出异常。
Java标注中,临时属性必须使用@Transient标注,既可以配置在临时属性上,也可以配置在对应的getter、setter方法上。例如:
@Transient
public int getCount() {// 临时属性,用于计算总记录数
return name == null ? 0 :count;
}
如果只有形如getter、setter的方法,但是没有对应的属性,Hibernate仍然会认为该属性存在。因此也需要用@Transient标注。
在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配置类型,例如:
或者直接用 配置日期版本,与上面的配置是等价的: