实体类与数据库之间存在某种映射关系,Hibernate依据这种映射关系完成数据的存取,因此映射关系的配置在Hibernate中是最关键的。Hibernate支持xml配置文件与@注解配置两种方式。xml配置文件是最基础的配置,而@注解是Java的官方JPA(Java Persistence API)提供的。本章分别使用@注解与xml讲解Hibernate的映射配置。
10.1 实体类的映射
从Java的角度讲,实体类就是普通的Java封装类(有人称为POJO有人称为VO)。仅从实体类中的代码信息,Hibernate并不能得知该实体类对应哪个数据表,因此还需要以某种方式配置一下。常用的方式有*.hbm.xml文件配置与@注解配置两种。
hbm.xml文件就是普通的xml文件,hbm为Hibernate
10.1.1 使用@注解配置实体类
实体类一般有ID、普通属性、集合属性等,分别对应数据库的主键、普通列、外键。@注解配置中,实体类用@Entity注解,用@Table指定对应的数据表,用@Id配置主键,用@Column配置普通属性,用@OneToMany、@ManyToOne、@OneToOne、@ManyToMany配置实体间关系等。实体类之间的关于在后面的章节中会做详细介绍。下面编写一个UsersVo类,在该类中使用@注解配置实体类的映射,UsersVo类的代码如下:
package
import
@Entity //
@Table(name
public
//
@Id //
@GeneratedValue(strategy
private
@Column(name
private
@Column(name
private
@Column(name
private
@Column(name
private
//
public
}
public
this.id
}
public
String
this.id
this.name
this.age
this.tel
this.address
}
public
return
}
public
this.address
}
public
return
}
public
this.age
}
public
return
}
public
this.id
}
public
return
}
public
this.name
}
public
return
}
public
this.tel
}
}
上述代码中,所有的@注解都是javax.persistence.*下的,而不是org.hibernate.*下的。javax.persistence.*下的注解为JPA规范规定的注解,用于标注实体类与数据库的映射关系,而org.hibernate.*下的注解仅用于补充,当某个功能JPA暂时不支持而Hibernate支持时使用。
10.1.2 使用XML文件配置实体类映射
多个实体类可以配置在一个XML文件中。Hibernate推荐XML映射文件和实体类同名,便于阅读和维护,比如UsersVo.java文件对应UsersVo.
<!DOCTYPE
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
-->
<hibernate-mapping>
<!--
</hibernate-mapping>
代码中的DOCTYPE指定的dtd文件位于hibernate.jar中。dtd文件为XML格式验证文件,Hibernate使用该dtd来验证该XML文件格式是否正确。如果hibernate.jar或者classpath中不存在该dtd文件,Hibernate会到指定的URL下载该文件。
在上述的映射文件中,<class>中的name表示实体类的路径和名称。table="”用来指定数据库表格
10.1.3 在hibernate.cfg.xml文件中配置实体类映射
实体类还需要配置到hibernate.cfg.xml中,以便Hibernate初始化实体类与数据库表的映射关系。如果只配置了映射关系,而没有配置到hibernate.cfg.xml中,Hibernate是无法解析实体类的,因为Hibernate无法自行判断哪些是实体类。
如果实体类是使用@注解配置的,需要用<mapping
<!--
<mapping
<mapping
<!--
<mapping
<mapping
10.1.4 配置主键映射
实体类最好有主键列,并有对应的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
//
@GeneratedValue(strategy
private
2.
如果使用XML配置,主键用<id
<id
<generator
</id>
10.1.5 主键生成规则
在10.1.4节中讲到了主键的配置,在配置主键过程中,配置了主键是否是自动生成的。@Id配置主键的同时,也要用@GeneratedValue配置主键生成规则。主键生成规则也成为主键生成策略,负责维护新实体的主键值。用的最多的策略是自增长策略。Hibernate还支持其他的多种主键生成规则。这些生成规则有些是数据库提供的,有些是Hibernate提供的。
1.
到目前为止,@注解只支持四种逐渐生成策略:GenerationType.AUTO、GenerationType.TABLE、GenerationType.
q
q
@Id
@TableGenerator(name
@GeneratedValue(strategy
private
q
@Id
@SequenceGenerator(name
@GeneratedValue(strategy
@Column(name
private
q
2.
XML配置中支持的主键生成规则,比使用@注解的配置的主键生成规则要多,XML配置中支持的主键生成规则有以下几种:
q
q
q
<id
</id>
q
q
<id
</id>
q
<id
</id>
q
q
q
q
q
MySQL数据库与Hibernate都提供自增长策略,但是原理是不太一样的。如果采用MySQL的自增长,插入数据时Hibernate生成的SQL语句中将不包含id主键列数据。该主键的当前值、下一个值由数据库自己维护。如果使用Hibernate的自增长,插入数据时Hibernate生成的SQL语句将包含id主键列,并由Hibernate维护该主键的当前值以及下一个值。
对于普通的应用来说,数据库自增长与Hibernate自增长在使用上没有区别。但是如果某数据库同时被两个Hibernate程序使用,那么此时使用Hibernate自增长将会出现错误。例如如果当前主键值为101,那么Hibernate会认为下个主键值为102,两个Hibernate程序插入数据时都会将主键值设为101,这时会因为主键冲突而导致其中一个写数据失败。
10.1.6 使用@注解配置普通属性映射
这里说的普通属性,是指除了主键外的、Java基本类型的属性,Integer类型与int类型是不同的,Integer默认为null,在数据库中也表现为null,而int默认为0,在数据库中也表现为0。
普通属性使用@Column与@Basic配置。二者都可以省略。如果省略,则全部按照默认的规则配置,@Column与@Basic的用法如下:
q
q
@Column与@Basic使用的代码示例如下:
@Column(name
@Basic(fetch
private
日期属性也是普通的属性,需要用@Basic声明加载方式、@Column等指定列名,二者都可省略。另外,如果日期属性是java.util.Date类型的,必须要用@Temporal配置日期类型,取值可以为Date、Time或者Timestemp。否则Hibernate将无法区分该类型是到底是java.sql.Date(只有年月日等日期信息)类型还是java.sql.Time(只有时分秒等时间信息)类型、还是java.sql.TimeStamp(既有日期信息、又有时间信息)类型。例如:
@Temporal(TemporalType.TIMESTAMP) //
@Column(name
private
在配置日期属性时,如果属性类型是java.util.Date类型,需要用@Temporal声明日期类型。但是如果是java.sql.Time、java.sql.Date或者java.sql.TimeStamp类型的,类型本身就已经很明确了,不再需要@Temporal声明了。
10.1.7 使用XML文件配置普通属性映射
XML中使用<property
<property
column="salary"
lazy="false"
</property>
在使用@注解配置中,如果没有对普通属性进行配置,则默认该属性名与数据表列名相同;而xml文件配置中,如果对普通属性没有配置,则认为该属性没有对应的数据库列,不参与持久化。二者是截然不同的。
在配置日期类型属性时,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(既有日期信息、又有时间信息)类型。示例代码如下:
<property
10.1.8 配置临时属性映射
实体类可能有一些临时属性,在JPA中被称为Transient属性。这些属性用于方便计算等其他用途,而不是保存数据到数据库中。这些属性必须被标记为Transient,以便Hibernate把它们区别对待。否则Hibernate会试图往数据库写该属性,可能会因对应的列不存在而抛出异常。
Java标注中,临时属性必须使用@Transient标注,既可以配置在临时属性上,也可以配置在对应的getter、setter方法上。例如:
@Transient
public
return
}
如果只有形如getter、setter的方法,但是没有对应的属性,Hibernate仍然会认为该属性存在。因此也需要用@Transient标注。
而在XML配置中,所有没有配置到XML文件中的属性都被视为临时属性。如果某属性漏配置了,该属性值将不被保存到数据库中。
10.2 Hibernate中的悲观锁和乐观锁
悲观锁和乐观锁用于处理数据的并发访问。Hibernate中有一种特殊的属性:版本(Version)属性。版本属性不参与业务逻辑,只用来保证不会有两个线程同时对该数据进行写操作。版本属性是乐观锁的一种实现方式。乐观锁是相对于悲观锁而言的。悲观锁与乐观锁都是保证数据准确性的机制。
10.2.1 什么是悲观锁
悲观锁假定其他用户企图访问或者改变你
为保证数据的准确性,程序必须保证在一个线程修改数据的时候,该数据没有被其他线程修改。在传统的数据库编程中,程序修改数据时先锁定该数据行,使其他程序无法修改该行数据,修改完毕后释放数据锁,以此保证数据准确性。由于该机制需要锁定数据行,被锁定的数据只能被一个线程使用,因此被称为悲观锁。
10.2.2 悲观锁的使用
Hibernate的悲观锁是使用SQL语句或者HQL语句实现的,下面是一个典型的倚赖数据库的悲观锁调用:
select
这条
下面的代码实现了对查询记录的加锁:
String
Query
query.setLockMode("users",LockMode.UPGRADE);
List
query.setLockMode
观察运行期
select
as
from
这里
10.2.3 什么是乐观锁
与悲观锁相反,乐观锁使用完全不同的方式。乐观锁通过Version列保存当前数据的版本,如果程序修改了数据,就将版本列加1。反过来,如果版本列有了变化,说明该数据被修改过了。程序保存数据时会检查数据的Version列。如果Version列已经发生了变化,程序会重新读取、修改并保存数据。由于该机制不需要锁定数据行,允许多条线程同时访问同一条数据,因此被称为乐观锁。乐观锁的效率要高于悲观锁,因此现代编程更倾向于乐观锁。
乐观锁则认为其他用户企图改变正在更改的对象的概率是很小的,因此乐观锁直到准备提交所作的更改时才将对象锁住,读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁可以用较大的锁粒度获得较好的并发访问性能
10.2.4 乐观锁的配置
Hibernate支持乐观锁,保存数据时Hibernate会自动完成检查Version列、修改数据、更新Version列等工作。Hibernate隐藏了所有的Version操作细节,只需要指定实体类的Version列即可。实体类中可用@Version配置版本属性。版本列一般为数字类型属性。例如:
@Version
private
XML中使用<version
<version
XML配置版本属性要比@配置灵活,版本属性既可以为int、long等数据类型,也可以为Timestamp时间戳等类型,配置时用type配置类型,例如:
<version
或者直接用<timestemp/>配置日期版本,与上面的配置是等价的:
<timestamp
10.3 本章小结
实体类与数据库之间存在某种映射关系,Hibernate依据这种映射关系完成数据的存取,因此映射关系的配置在Hibernate中是最关键的。本章中对实体类的映射做了简单介绍,实体类的映射有两种方式,一个使用@注解,一是使用XML文件配置。本章还介绍了Hibernate中的悲观锁和乐观锁。下一章将详细讲述多对一、一对多、一对一、多对多等映射关系。