既来于斯,则归于斯。希望能聊有所用。
本文并非试图替代Hibernate Reference,相对而言,Hibernate Reference
的编写目的是为开发者提供更简便的条目索引,而本文目标则在于为开
发人员提供一个入门和把握Hibernate的途径。
本文需结合Hibernate Reference使用。
笔者好友曹晓钢义务组织了Hibernate文档的汉化工作,在此对其辛勤劳作致敬。
中文版Hibernate Reference将被包含在Hibernate下个官方Release中,目前可
通过http://www.redsaga.com获得中文版Hibernate Reference的最新版本。
本文中假如发现问题和错误,请随时联系笔者,以免误导他人。
本文转载不限,不过请保持本文完整。万分感谢!
Hibernate 开发指南.......................................................................................................1
预备工作..........................................................................................................3
构建Hibernate基础代码...............................................................................3
由数据库产生基础代码...........................................................................4
Hibernate配置..............................................................................................15
第一段代码....................................................................................................17
Hibernate基础语义......................................................................................19
Configuration ........................................................................................19
SessionFactory.......................................................................................20
Session....................................................................................................20
Hibernate高级特性......................................................................................................22
XDoclet与Hibernate映射...........................................................................22
数据检索........................................................................................................31
Criteria Query...............................................................................31
Criteria查询表达式................................................................31
Criteria高级特性....................................................................33
限定返回的记录范围.............................................................33
对查询结果进行排序.............................................................33
Hibernate Query Language (HQL).........................................34
数据关联........................................................................................................35
一对一关联.............................................................................35
一对多关联.............................................................................37
Ø 单向一对多关系......................................................37
Ø 双向一对多关系......................................................42
多对多关联.............................................................................47
数据访问........................................................................................................54
PO和VO...............................................................................................54
关于unsaved-value ...............................................................................57
Inverse和Cascade.........................................................................59
延迟加载(Lazy Loading)............................................................59
事务管理........................................................................................................63
基于JDBC的事务管理:.....................................................................64
基于JTA的事务管理:.......................................................................65
锁(locking).........................................................................................68
悲观锁(Pessimistic Locking).......................................68
乐观锁(Optimistic Locking)..........................................69
Hibernate分页..........................................................................................73
Cache管理....................................................................................................75
Session管理...............................................................................................79
编后赘言................................................................................................................84
Hibernate Quick Start
预备工作
1. 下载Ant软件包,解压缩(如C:\ant\)。并将其bin目录(如c:\ant\bin)添加到系统
PATH 中。
2. 下载Hibernate、Hibernate-Extension和Middlegen-Hibernate软件包的最新版本。
http://prdownloads.sourceforge.net/hibernate/
构建Hibernate 基础代码
Hibernate基础代码包括:
1. POJO
POJO 在Hibernate 语义中理解为数据库表所对应的Domain Object。这里的POJO
就是所谓的“Plain Ordinary Java Object”,字面上来讲就是无格式普通Java 对象,简
单的可以理解为一个不包含逻辑代码的值对象(Value Object 简称VO)。
一个典型的POJO:
public class TUser implements Serializable {
private String name;
public User(String name) {
this.name = name;
}
/** default constructor */
public User() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
2. Hibernate 映射文件
Hibernate 从本质上来讲是一种“对象-关系型数据映射”(Object Relational
Mapping 简称ORM)。前面的POJO在这里体现的就是ORM中Object层的语义,
而映射(Mapping)文件则是将对象(Object)与关系型数据(Relational)相关联
的纽带,在Hibernate中,映射文件通常以“.hbm.xml”作为后缀。
构建Hibernate基础代码通常有以下途径:
1. 手工编写
2. 直接从数据库中导出表结构,并生成对应的ORM文件和Java 代码。
这是实际开发中最常用的方式,也是这里所推荐的方式。
通过直接从目标数据库中导出数据结构,最小化了手工编码和调整的可能性,从而
最大程度上保证了ORM文件和Java 代码与实际数据库结构相一致。
3. 根据现有的Java 代码生成对应的映射文件,将Java 代码与数据库表相绑定。
通过预先编写好的POJO 生成映射文件,这种方式在实际开发中也经常使用,非凡
是结合了xdoclet 之后显得尤为灵活,其潜在问题就是与实际数据库结构之间可能
出现的同步上的障碍,由于需要手工调整代码,往往调整的过程中由于手工操作的
疏漏,导致最后生成的配置文件错误,这点需要在开发中非凡注重。
结合xdoclet,由POJO 生成映射文件的技术我们将在“高级特性”章节中进行探讨。
由数据库产生基础代码
通过Hibernate官方提供的MiddleGen for Hibernate 和Hibernate_Extension工具包,我
们可以很方便的根据现有数据库,导出数据库表结构,生成ORM和POJO。
1) 首先,将Middlegen-Hibernate软件包解压缩( 如解压缩到C:\Middlegen\ )。
2) 配置目标数据库参数
进入MiddleGen 目录下的\config\database 子目录,根据我们实际采用的数据库打开
对应的配置文件。如这里我们用的是mysql数据库,对应的就是mysql.xml 文件。
<property name="database.script.file"
value="${src.dir}/sql/${name}-mysql.sql"/>
<property name="database.driver.file"
value="${lib.dir}/mysql.jar"/>
<property name="database.driver.classpath"
value="${database.driver.file}"/>
<property name="database.driver"
value="org.gjt.mm.mysql.Driver"/>
<property name="database.url"
value="jdbc:mysql://localhost/sample"/>
<property name="database.userid"
value="user"/>
<property name="database.password"
value="mypass"/>
<property name="database.schema"
value=""/>
<property name="database.catalog"
value=""/>
<property name="jboss.datasource.mapping"
value="mySQL"/>
其中下划线标准的部分是我们进行配置的内容,分别是数据url以及数据库用
户名和密码。
3) 修改Build.xml
修改MiddleGen 根目录下的build.xml 文件,此文件是Middlegen-Hibernate 的Ant
构建配置。Middlegen-Hibernate将根据build.xml 文件中的具体参数生成数据库表映射
文件。可配置的项目包括:
a) 目标数据库配置文件地址
查找要害字 ”!ENTITY”,得到:
<!DOCTYPE project [
<!ENTITY database SYSTEM
"file:./config/database/hsqldb.xml">
]>
默认情况下,MiddleGen 采用的是hsqldb.xml,将其修改为我们所用的数据
库配置文件(mysql.xml):
<!DOCTYPE project [
<!ENTITY database SYSTEM
"file:./config/database/mysql.xml">
]>
b) Application name
查找:
<property name="name" value="airline"/>
“aireline”是MiddleGen原始配置中默认的 Application Name,将其修改为我们
所希望的名称,如“HibernateSample”:
<property name="name" value="HibernateSample"/>
c) 输出目录
查找要害字“name="build.gen-src.dir"”,得到:
<property name="build.gen-src.dir"
value="${build.dir}/gen-src"/>
修改value="${build.dir}/gen-src"使其指向我们所期望的输出目录,
这里我们修改为:
<property name="build.gen-src.dir"
value="C:\sample"/>
d) 对应代码的Package name
查找要害字“destination”,得到:
<hibernate
destination="${build.gen-src.dir}"
package="${name}.hibernate"
genXDocletTags="false"
genIntergratedCompositeKeys="false"
javaTypeMapper=
"middlegen.plugins.hibernate.HibernateJavaTypeMapper"
/>
可以看到,hibernate 节点package 属性的默认设置实际上是由前面的
Application Name (${name})和“.hibernate”组合而成,根据我们的需要,
将其改为:
<hibernate
destination="${build.gen-src.dir}"
package="org.hibernate.sample"
genXDocletTags="true"
genIntergratedCompositeKeys="false"
javaTypeMapper=
"middlegen.plugins.hibernate.HibernateJavaTypeMapper"
/>
这里还有一个属性genXDocletTags,假如设置为true,则生成的代码将包含
xdoclet tag,这为以后在开发过程中借助xdoclet进行映射调整提供了帮助。关
于Hibernate的xdoclet使用,请参见“高级特性”中的相关内容。
注重,假如使用的数据库为SQLServer,需要将build.xml 中如下部分(下划
线部分)删除,否则Middlegen会报出找不到表的错误。
<middlegen
appname="${name}"
prefsdir="${src.dir}"
gui="${gui}"
databaseurl="${database.url}"
initialContextFactory="${java.naming.factory.initial}"
providerURL="${java.naming.provider.url}"
datasourceJNDIName="${datasource.jndi.name}"
driver="${database.driver}"
username="${database.userid}"
password="${database.password}"
schema="${database.schema}"
catalog="${database.catalog}"
>
至此为止,MiddleGen 已经配置完毕,在MiddleGen 根目录下运行ant,就将出现
MiddleGen的界面:
可以看到,数据库中的表结构已经导入到MiddleGen 的操作界面中,选定数据库
表视图中的表元素,我们即可调整各个数据库表的属性。
1 Domain Class Name
对应POJO 的类名
2 Key Generator
主键产生器
可选项说明:
1) Assigned
①
② ③
④
⑤
⑥ ⑦
⑨
⑧
⑩
主键由外部程序负责生成,无需Hibernate参与。
2) hilo
通过hi/lo 算法实现的主键生成机制,需要额外的数据库表保存主
键生成历史状态。
3) seqhilo
与hilo 类似,通过hi/lo 算法实现的主键生成机制,只是主键历史
状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle。
4) increment
主键按数值顺序递增。此方式的实现机制为在当前应用实例中维持
一个变量,以保存着当前的最大值,之后每次需要生成主键的时候
将此值加1作为主键。
这种方式可能产生的问题是:假如当前有多个实例访问同一个数据
库,那么由于各个实例各自维护主键状态,不同实例可能生成同样
的主键,从而造成主键反复异常。因此,假如同一数据库有多个实
例访问,此方式必须避免使用。
5) identity
采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL
中的主键生成机制。
6) sequence
采用数据库提供的sequence 机制生成主键。如Oralce 中的
Sequence。
7) native
由Hibernate根据底层数据库自行判定采用identity、hilo、sequence
其中一种作为主键生成方式。
8) uuid.hex
由Hibernate基于128 位唯一值产生算法生成16 进制数值(编码后
以长度32 的字符串表示)作为主键。
9) uuid.string
与uuid.hex类似,只是生成的主键未进行编码(长度16)。在某些
数据库中可能出现问题(如PostgreSQL)。
10) foreign
使用外部表的字段作为主键。
一般而言,利用uuid.hex 方式生成主键将提供最好的性能和数据库平台适
应性。
另外由于常用的数据库,如Oracle、DB2、SQLServer、MySql 等,都提
供了易用的主键生成机制(Auto-Increase 字段或者Sequence)。我们可以在数
据库提供的主键生成机制上,采用generator-class=native的主键生成方式。
不过值得注重的是,一些数据库提供的主键生成机制在效率上未必最佳,
大量并发insert数据时可能会引起表之间的互锁。
数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状
态(如对于自增型主键而言,此内部表中就维护着当前的最大值和递增量),
之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之
后再把这个新的最大值更新回内部表中,这样,一次Insert操作可能导致数据
库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生
了较大影响。
因此,对于并发Insert要求较高的系统,推荐采用uuid.hex 作为主键生成
机制。
3 假如需要采用定制的主键产生算法,则在此处配置主键生成器,主键生成器必
须实现net.sf.hibernate.id.IdentifierGenerator 接口。
4 Schema Name
数据库Schema Name。
5 Persister
自定义持久类实现类类名。假如系统中还需要Hibernate 之外的持久层实
现机制,如通过存储过程得到目标数据集,甚至从LDAP中获得数据来填
充我们的POJO。
6 Enable proxies
是否使用代理(用于延迟加载[Lazy Loading])。
7 Dynamic Update
假如选定,则生成Update SQL 时不包含未发生变动的字段属性,这样可
以在一定程度上提升SQL执行效能。
8 Mutable
类是否可变,默认为选定状态(可变)。假如不希望应用程序对此类对应
的数据记录进行修改(如对于数据库视图),则可将取消其选定状态,之
后对此类的Delete和Update操作都将失效。
9 Implement the Lifecyle interface
是否实现Lifecyle接口。Lifecyle接口提供了数据固化过程中的控制机制,
通过实现Lifecyle接口,我们可以在数据库操作中加入回调(Call Back)
机制,如在数据库操作之前,之后触发指定操作。
10 Implement the Validatable interface
是否实现Validatable接口。通过实现Validatable接口,我们可以在数据被
固化到数据库表之前对其合法性进行验证。
值得注重的是,通过实现Lifecyle接口,我们同样可以在数据操作之前验
证数据合法性,不同的是,Validatable 接口中定义的validate 方法可能会
被调用多次,因此设计中应避免在Validatable 接口的validate 方法实现中
加入业务逻辑的验证。
以上是针对Class的设置,同样,在MiddleGen中,我们也可以设定字段属性。在
MiddleGen中选定某个字段,界面下方即出现字段设置栏:
在这里我们可以设置字段的属性,其中:
1 Hibernate mapping specialty
映射类型:
Key :主键
Property :属性
Version :用于实现optimistic locking,参见“高级特性”章节中关
于optimistic locking的描述
2 Java property name
①
②
③
④ ⑤
字段对应的Java 属性名
3 Java Type
字段对应的Java 数据类型
4 Column updateable
生成Update SQL时是否包含本字段。
5 Column insertable
生成Insert SQL时是否包含本字段。
单击窗口顶部的Generate 按钮,MiddleGen 即为我们生成这些数据库表所对应的
Hibernate映射文件。在MiddleGen根目录下的\build\gen-src\net\hibernate\sample目录中,
我们可以看到对应的以.hbm.xml 作为后缀的多个映射文件,每个映射文件都对应了数
据库的一个表。
仅有映射文件还不够,我们还需要根据这些文件生成对应的POJO。
POJO 的生成工作可以通过Hibernate Extension 来完成,Hibernate Extension 的
tools\bin目录下包含三个工具:
1. hbm2java.bat
根据映射文件生成对应的POJO。通过MiddleGen 我们已经得到了映射文件,
下一步就是通过hbm2java.bat工具生成对应的POJO。
2. class2hbm.bat
根据POJO class 生成映射文件,这个工具很少用到,这里也就不再具体介绍。
3. ddl2hbm.bat
由数据库导出库表结构,并生成映射文件以及POJO。这个功能与MiddleGen
的功能重叠,但由于目前还不够成熟(实际上已经被废弃,不再维护),提供
的功能也有限,所以我们还是采用MiddleGen生成映射文件,之后由hbm2java
根据映射文件生成POJO 的方式。
为了使用以上工具,首先我们需要配置一些参数,打开tools\bin\setenv.bat 文件,修改
其中的JDBC_DRIVER和HIBERNATE_HOME环境变量,使其指向我们的实际JDBC Driver
文件和Hibernate所在目录,如
set JDBC_DRIVER=c:\mysql\mysql.jar
set HIBERNATE_HOME=c:\hibernate
同时检查一下环境变量CP中的各个项目中是否实际存在,非凡是%CORELIB%下的jar
文件,某些版本的发行包中,默认配置中的文件名与实际的文件名有所出入(如
%CORELIB%\commons-logging.jar, 在Hibernate 发行包中,可能实际的文件名是
commons-logging-1.0.3.jar,诸如此类)。
使用hbm2java,根据MiddleGen生成的映射文件生成Java 代码:
打开Command Window,在tools\bin目录下执行:
hbm2java c:\sample\org\hibernate\sample\*.xml --output=c:\sample\
即可生成对应的POJO。生成的POJO 保存在我们指定的输出目录下(c:\sample)。
目前为止,我们已经完成了通过MiddleGen 产生Hibernate 基础代码的工作。配置
MiddleGen 也许并不是一件轻松的事情,对于Eclipse 的用户而言,目前已经出现了好几个
Hibernate 的Plugin,通过这些Plugin 我们可以更加轻松的完成上述工作,具体的使用方式
请参见附录。
Hibernate 配置
前面已经得到了映射文件和POJO,为了使Hibernate 能真正运作起来,我们还需要一
个配置文件。
Hibernate同时支持xml格式的配置文件,以及传统的properties 文件配置方式,不过这
里建议采用xml 型配置文件。xml配置文件提供了更易读的结构和更强的配置能力,可以直
接对映射文件加以配置,而在properties 文件中则无法配置,必须通过代码中的Hard Coding
加载相应的映射文件。下面假如不作非凡说明,都指的是基于xml格式文件的配置方式。
配置文件名默认为“hibernate.cfg.xml”(或者hibernate.properties),Hibernate 初始化期
间会自动在CLASSPATH 中寻找这个文件,并读取其中的配置信息,为后期数据库操作做好
预备。
配置文件应部署在CLASSPATH 中,对于Web 应用而言,配置文件应放置在在
\WEB-INF\classes 目录下。
一个典型的hibernate.cfg.xml配置文件如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.
dtd">
<hibernate-configuration>
<!—- SessionFactory 配置 -->
<session-factory>
<!—- 数据库URL -->
<property name="hibernate.connection.url">
jdbc:mysql://localhost/sample
</property>
<!—- 数据库JDBC驱动 -->
<property name="hibernate.connection.driver_class">
org.gjt.mm.mysql.Driver
</property>
<!—- 数据库用户名 -->
<property name="hibernate.connection.username">
User
</property>
<!—- 数据库用户密码 -->
<property name="hibernate.connection.password">
Mypass
</property>
<!--dialect ,每个数据库都有其对应的Dialet以匹配其平台特性 -->
<property name="dialect">
net.sf.hibernate.dialect.MySQLDialect
</property>
<!—- 是否将运行期生成的SQL输出到日志以供调试 -->
<property name="hibernate.show_sql">
True
</property>
<!—- 是否使用数据库外连接 -->
<property name="hibernate.use_outer_join">
True
</property>
<!—- 事务管理类型,这里我们使用JDBC Transaction -->
<property name="hibernate.transaction.factory_class">
net.sf.hibernate.transaction.JDBCTransactionFactory
</property>
<!—映射文件配置,注重配置文件名必须包含其相对于根的全路径 -->
<mapping resource="net/xiaxin/xdoclet/TUser.hbm.xml"/>
<mapping resource="net/xiaxin/xdoclet/TGroup.hbm.xml"/>
</session-factory>
</hibernate-configuration>
一个典型的hibernate.properties配置文件如下:
hibernate.dialect net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class org.gjt.mm.mysql.Driver
hibernate.connection.driver_class com.mysql.jdbc.Driver
hibernate.connection.url jdbc:mysql:///sample
hibernate.connection.username user
hibernate.connection.password mypass
第一段代码
上面我们已经完成了Hiberante 的基础代码,现在先从一段最简单的代码入手,感受一
下Hibernate所提供的强大功能。
下面这段代码是一个JUnit TestCase,演示了TUser 对象的保存和读取。考虑到读者可
能没有JUnit的使用经验,代码中加入了一些JUnit相关注释。
public class HibernateTest extends TestCase {
Session session = null;
/**
* JUnit中setUp方法在TestCase初始化的时候会自动调用
* 一般用于初始化公用资源
* 此例中,用于初始化Hibernate Session
*/
protected void setUp(){
try {
/**
* 采用hibernate.properties配置文件的初始化代码:
* Configuration config = new Configuration();
* config.addClass(TUser.class);
*/
//采用hibernate.cfg.xml配置文件
//请注重初始化Configuration时的差异:
// 1.Configuration的初始化方式
// 2.xml文件中已经定义了Mapping文件,因此无需再Hard Coding导入
// POJO文件的定义
Configuration config = new Configuration().configure();
SessionFactory sessionFactory =
config.buildSessionFactory();
session = sessionFactory.openSession();
} catch (HibernateException e) {
e.printStackTrace();
}
}
/**
* 与setUp方法相对应,JUnit TestCase执行完毕时,会自动调用tearDown方法
* 一般用于资源释放
* 此例中,用于关闭在setUp方法中打开的Hibernate Session
*/
protected void tearDown(){
try {
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
/**
* 对象持久化(Insert)测试方法
*
* JUnit中,以”test”作为前缀的方法为测试方法,将被JUnit自动添加
* 到测试计划中运行
*/
public void testInsert(){
try {
TUser user = new TUser();
user.setName("Emma");
session.save(user);
session.flush();
} catch (HibernateException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
/**
* 对象读取(Select)测试
* 请保证运行之前数据库中已经存在name=’Erica’的记录
*/
public void testSelect(){
String hql=
" from TUser where name='Erica'";
try {
List userList = session.find(hql);
TUser user =(TUser)userList.get(0);
Assert.assertEquals(user.getName(),"Erica");
} catch (HibernateException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
}
主流IDE,如Eclipse、Intellij IDEA 和JBuilder 中都内置了JUnit支持。下面是Eclipse
中运行该代码的结果(在Run菜单中选择Run as -> JUnit Test即可):
现在我们已经成功实现了一个简单的TUser 实例的保存和读取。可以看到,程序中通过
少量代码实现了Java 对象和数据库数据的同步,同时借助Hibernate的有力支持,轻松实现
了对象到关系型数据库的映射。
相对传统的JDBC数据访问模式,这样的实现无疑更符合面向对象的思想,同时也大大
提高了开发效率。
上面的代码中引入了几个Hibernate基础语义:
1. Configuration
2. SessionFactory
3. Session
下面我们就这几个要害概念进行探讨。
Hibernate基础语义
Configuration
正如其名,Configuration 类负责管理Hibernate 的配置信息。Hibernate 运行时需要
获得一些底层实现的基本信息,其中几个要害属性包括:
1. 数据库URL
2. 数据库用户
3. 数据库用户密码
4. 数据库JDBC驱动类
5. 数据库dialect,用于对特定数据库提供支持,其中包含了针对特定数据库特性
的实现,如Hibernate数据类型到特定数据库数据类型的映射等。
使用Hibernate 必须首先提供这些基础信息以完成初始化工作,为后继操作做好准
备。这些属性在hibernate配置文件(hibernate.cfg.xml 或hibernate.properties)中加以设
定(参见前面“Hibernate配置”中的示例配置文件内容)。
当我们调用:
Configuration config = new Configuration().configure();
时,Hibernate会自动在当前的CLASSPATH 中搜寻hibernate.cfg.xml 文件并将其读
取到内存中作为后继操作的基础配置。Configuration 类一般只有在获得SessionFactory
时需要涉及,当获得SessionFactory 之后,由于配置信息已经由Hibernate 维护并绑定
在返回的SessionFactory之上,因此一般情况下无需再对其进行操作。
我们也可以指定配置文件名,假如不希望使用默认的hibernate.cfg.xml 文件作为配
置文件的话:
File file = new File("c:\\sample\\myhibernate.xml");
Configuration config = new Configuration().configure(file);
SessionFactory
SessionFactory 负责创建Session 实例。我们可以通过Configuation 实例构建
SessionFactory:
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Configuration实例config会根据当前的配置信息,构造SessionFactory实例并返回。
SessionFactory 一旦构造完毕,即被赋予特定的配置信息。也就是说,之后config 的任
何变更将不会影响到已经创建的SessionFactory 实例(sessionFactory)。假如需要
使用基于改动后的config 实例的SessionFactory,需要从config 重新构建一个
SessionFactory实例。
Session
Session是持久层操作的基础,相当于JDBC中的Connection。
Session实例通过SessionFactory实例构建:
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
之后我们就可以调用Session所提供的save、find、flush等方法完成持久层操作:
Find:
String hql= " from TUser where name='Erica'";
List userList = session.find(hql);
Save:
TUser user = new TUser();
user.setName("Emma");
session.save(user);
session.flush();
最后调用Session.flush方法强制数据库同步,这里即强制Hibernate将user实
例立即同步到数据库中。假如在事务中则不需要flush方法,在事务提交的时候,
hibernate自动会执行flush方法,另外当Session关闭时,也会自动执行flush方法。
Hibernate高级特性
XDoclet 与Hibernate 映射
在POJO 中融合XDoclet 的映射文件自动生成机制,提供了除手动编码和由数据库导出
基础代码的第三种选择。
本章将结合XDoclet对Hibernate中的数据映射进行介绍。
实际开发中,往往首先使用MiddleGen 和hbm2java 工具生成带有XDoclet tag的POJO
(MiddleGen build.xml中的genXDocletTags选项决定了是否在映射文件中生成XDoclet Tag,
详见Hibernate Quick Start章节中关于MiddleGen的说明)。之后通过修改POJO中的XDoclet
tag进行映射关系调整。
XDoclet已经广泛运用在EJB开发中,在其最新版本里,包含了一个为Hibernate提供支
持的子类库Hibernate Doclet,其中包含了生成Hibernate映射文件所需的ant构建支持以及
java doc tag支持。
XDoclet实现基本原理是,通过在Java代码加入特定的JavaDoc tag,从而为其添加特定
的附加语义,之后通过XDoclet工具对代码中JavaDoc Tag进行分析,自动生成与代码对应
的配置文件,XDoclet。
在Hibernate-Doclet中,通过引入Hibernate相关的JavaDoc tag,我们就可以由代码生成
对应的Hibernate映射文件。
下面是一个代码片断,演示了Hibernate-Doclet的使用方式:
/**
* @hibernate.class
* table="TUser"
*/
public class TUser implements Serializable {
……
/**
* @hibernate.property
* column="name"
* length="50"
* not-null="true"
*
* @return String
*/
public String getName() {
return this.name;
}
……
}
以上是使用Hibernate-Doclet 描述POJO(TUser)及其对应表(TUser)之间映射关系
的一个例子。
其中用到了两个hibernate doclet tag,@hibernate.class和@hibernate.property。
这两个tag分别描述了POJO所对应的数据库表信息,以及其字段对应的库表字段信息。
之后Hibernate Doclet就会根据这些信息生成映射文件:
<
hibernate-mapping>
<class
name="net.xiaxin.xdoclet.TUser"
table="TUser"
>
<property
name="name"
type="java.lang.String"
column="name"
not-null="true"
length="50"
>
</class>
</hibernate-mapping>
这样我们只需要维护Java 代码,而无需再手动编写具体的映射文件即可完成Hibernate
基础代码。
熟记Hibernate-Doclet 众多的Tag,显然不是件轻松的事情,好在目前的主流IDE 都提
供了Live Template支持。我们只需进行一些配置工作,就可以实现Hibernate-Doclet Tag
的自动补全功能,从而避免了手工编写过程中可能出现的问题。
附录中提供了主流IDE,包括JBuilder,Intellij IDEA,Eclipse的Hibernate-Doclet集成
指南。
下面我们就Hibernate Doclet 中常用的Tag 进行探讨,关于Tag 的具体参考,请参见
XDoclet 的官方指南(http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html)以及
Hibernate Reference(http://www.hibernate.org)。
常用Hibernate-Doclet Tag介绍:
1. Class 层面:
1) @hibernate.class
描述POJO 与数据库表之间的映射关系,并指定相关的运行参数。
参数 描述 类型 必须
table 类对应的表名
默认值:当前类名
Text N
dynamic-update 生成Update SQL时,仅包含发生变动
的字段
默认值: false
Bool N
dynamic-insert 生成Insert SQL时,仅包含非空(null)
字段
默认值:false
Bool N
Proxy 代理类
默认值:空
Text N
discriminator-value 子类辨别标识,用于多态支持。 Text N
where 数据甄选条件,假如只需要处理库表中某
些特定数据的时候,可通过此选项设定结
果集限定条件。
如用户表中保存了全国所有用户的数据,
而我们的系统只是面向上海用户,则可指
定where=”location=’Shanghai’"
Text N
典型场景:
/**
* @hibernate.class
* table="TUser" (1)
* dynamic-update="true" (2)
* dynamic-insert="true" (3)
* proxy=”” (4)
* discriminator-value=”1” (5)
*/
public class TUser implements Serializable {
……
}
本例中:
1 table参数指定了当前类(TUser)对应数据库表“TUser”。
2 dynamic-update 参数设定为生成Update SQL 时候,只包括当前发生变化的
字段(提高DB Update性能)。
3 Dynamic-insert 参数设定为生成Insert SQL 时候,只包括当前非空字段。
(提高DB Insert性能)
4 Proxy 参数为空,表明当前类不使用代理(Proxy)。代理类的作用是为Lazy
Loading提供支持,请参见下面关于Lazy Loading的有关内容。
5 discriminator-value参数设为”1”。
discriminator-value 参数的目的是对多态提供支持。请参见下面关于
@hibernate.discriminator的说明。
2) @hibernate.discriminator
@hibernate.discriminator(识别器) 用于提供多态支持。
参数 描述 类型 必须
column 用于区分各子类的字段名称。
默认值:当前类名
text Y
type 对应的Hibernate类型 Bool N
length 字段长度 Bool N
如:
TUser类对应数据库表TUser,并且User类有两个派生类SysAdmin、
SysOperator。
在TUser表中, 根据user_type字段区分用户类型。
为了让Hibernate根据user_type能自动识别对应的Class类型(如 user_type==1
则自动映射到SysAdmin类,user_type==2 则自动映射到SysOperator类),我们需要
在映射文件中进行配置,而在Hibernate-Doclet中,对应的就是
@hibernate.discriminator 标识和 @hibernate.class 以及 @hibernate.subclass 的
discriminator-value属性。
典型场景:
/**
*
* @hibernate.class
* table="TUser"
* dynamic-update="true"
* dynamic-insert="true"
*
* @hibernate.discriminator column="user_type" type="integer"
*/
public class TUser implements Serializable {
……
}
根类TUser 中,通过@hibernate.discriminator 指定了以"user_type"字段
作为识别字段。
/**
* @hibernate.subclass
* discriminator-value="1"
*/
public class SysAdmin extends TUser {
……
}
/**
* @hibernate.subclass
* discriminator-value="2"
*/
public class SysOperator extends TUser {
……
}
SysAdmin 和SysOperator 均继续自TUser,其discriminator-value 分别设置
为"1"和"2",运行期Hibernate 在读取t_user 表数据时,会根据其user_type 字段进行
判定,假如是1 的话则映射到SysAdmin类,假如是2 映射到SysOperator 类。
上例中,描述SysAdmin 和SysOperator 时,我们引入了一个Tag:
@hibernate.subclass,顾名思义,@hibernate.subclass与@hibernate.class
不同之处就在于,@hibernate.subclass 描述的是一个子类,实际上,这两个Tag
除去名称不同外,并没有什么区别。
2. Method层面:
1) @hibernate.id
描述POJO 中要害字段与数据库表主键之间的映射关系。
参数 描述 类型 必须
column 主键字段名
默认值:当前类名
Text N
type 字段类型。
Hibernate总是使用对象型数据类型作
为字段类型,如int对应Integer,因此
这里将id设为基本类型[如int]以避免对
象创建的开销的思路是没有实际意义的,
即使这里设置为基本类型,Hibernate内
部还是会使用对象型数据对其进行处理,
只是返回数据的时候再转换为基本类型
而已。
Text N
length 字段长度 Text N
unsaved-value 用于对象是否已经保存的判定值。
详见“数据访问”章节的相关讨论。
Text N
generator-class 主键产生方式(详见Hibernate Quick
Start中关于MiddleGen的相关说明)
取值可为下列值中的任意一个:
assigned
hilo
seqhilo
increment
identity
sequence
native
uuid.hex
uuid.string
foreign
Text Y
2) @hibernate.property
描述POJO 中属性与数据库表字段之间的映射关系。
参数 描述 类型 必须
column 数据库表字段名
默认值:当前类名
Text N
type 字段类型 Text N
length 字段长度 Text N
not-null 字段是否答应为空 Bool N
unique 字段是否唯一(是否答应反复值) Bool N
insert Insert 操作时是否包含本字段数据
默认:true
Bool N
update Update操作时是否包含本字段数据
默认:true
Bool N
典型场景:
/**
* @hibernate.property
* column="name"
* length="50"
* not-null="true"
*
* @return String
*/
public String getName() {
return this.name;
}
注重:在编写代码的时候请,对将POJO的getter/setter方法设定为public,假如
设定为private,Hibernate将无法对属性的存取进行优化,只能转而采用传统的反射机制
进行操作,这将导致大量的性能开销(非凡是在1.4之前的Sun JDK版本以及IBM JDK中,
反射所带来的系统开销相当可观)。
包含XDoclet Tag的代码必须由xdoclet程序进行处理以生成对应的映射文件,
xdoclet的处理模块可通过ant进行加载,下面是一个简单的hibernate xdoclet的ant
构建脚本(注重实际使用时需要根据实际情况对路径和CLASSPATH设定进行调整):
<?xml version="1.0"?>
<project name="Hibernate" default="hibernate" basedir=".">
<property name="xdoclet.lib.home"
value="C:\xdoclet-1.2.1\lib"/>
<target name="hibernate" depends=""
description="Generates Hibernate class descriptor files.">
<taskdef name="hibernatedoclet"
classname="xdoclet.modules.hibernate.HibernateDocletTask">
<classpath>
<fileset dir="${xdoclet.lib.home}">
<include name="*.jar"/>
</fileset>
</classpath>
</taskdef>
<hibernatedoclet
destdir="./src/"
excludedtags="@version,@author,@todo"
force="true"
verbose="true"
mergedir=".">
<fileset dir="./src/">
<include name="**/hibernate/sample/*.java"/>
</fileset>
<hibernate version="2.0"/>
</hibernatedoclet>
</target>
</project>
除了上面我们介绍的Hibernate Doclet Tag,其他还有:
Class层面;
@hibernate.cache
@hibernate.jcs-cache
@hibernate.joined-subclass
@hibernate.joined-subclass-key
@hibernate.query
Method层面
@hibernate.array
@hibernate.bag
@hibernate.collection-cache
@hibernate.collection-composite-element
@hibernate.collection-element
@hibernate.collection-index
@hibernate.collection-jcs-cache
@hibernate.collection-key
@hibernate.collection-key-column
@hibernate.collection-many-to-many
@hibernate.collection-one-to-many
@hibernate.column
@hibernate.component
@hibernate.generator-param
@hibernate.index-many-to-many
@hibernate.list
@hibernate.many-to-one
@hibernate.map
@hibernate.one-to-one
@hibernate.primitive-array
@hibernate.set
@hibernate.timestamp
@hibernate.version
具体的Tag描述请参见XDoclet官方网站提供的Tag说明1。下面的Hibernate高级特性介
绍中,我们也将涉及到这些Tag的实际使用。
1 http://xdoclet.sourceforge.net/xdoclet/tags/hibernate-tags.html
数据检索
数据查询与检索是Hibernate中的一个亮点。相对其他ORM实现而言,Hibernate
提供了灵活多样的查询机制。其中包括:
1. Criteria Query
2. Hibernate Query Language (HQL)
3. SQL
Criteria Query
Criteria Query通过面向对象化的设计,将数据查询条件封装为一个对象。简单来
讲,Criteria Query可以看作是传统SQL的对象化表示,如:
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
criteria.add(Expression.eq("sex",new Integer(1)));
这里的criteria 实例实际上是SQL “Select * from t_user where
name=’Erica’ and sex=1”的封装(我们可以打开Hibernate 的show_sql 选项,
以观察Hibernate在运行期生成的SQL语句)。
Hibernate 在运行期会根据Criteria 中指定的查询条件(也就是上面代码中通过
criteria.add方法添加的查询表达式)生成相应的SQL语句。
这种方式的特点是比较符合Java 程序员的编码习惯,并且具备清楚的可读性。正因
为此,不少ORM实现中都提供了类似的实现机制(如Apache OJB)。
对于Hibernate的初学者,非凡是对SQL了解有限的程序员而言,Criteria Query
无疑是上手的极佳途径,相对HQL,Criteria Query提供了更易于理解的查询手段,借
助IDE的Coding Assist机制,Criteria的使用似乎不用太多的学习。
Criteria 查询表达式
Criteria 本身只是一个查询容器,具体的查询条件需要通过Criteria.add
方法添加到Criteria实例中。
如前例所示,Expression 对象具体描述了查询条件。针对SQL 语法,
Expression提供了对应的查询限定机制,包括:
方法 描述
Expression.eq 对应SQL“field = value”表达式。
如Expression.eq("name","Erica")
Expression.allEq 参数为一个Map对象,其中包含了多个属性-值对
应关系。相当于多个Expression.eq关系的叠加。
Expression.gt 对应SQL中的 “field > value ” 表达式
Expression.ge 对应SQL中的 “field >= value” 表达式
Expression.lt 对应SQL中的 “field < value” 表达式
Expression.le 对应SQL中的 “field <= value” 表达式
Expression.between 对应SQL中的 “between” 表达式
如下面的表达式表示年龄(age)位于13到50区
间内。
Expression.between("age",new
Integer(13),new Integer(50));
Expression.like 对应SQL中的 “field like value” 表达式
Expression.in 对应SQL中的 ”field in …” 表达式
Expression.eqProperty 用于比较两个属性之间的值,对应SQL中的“field
= field”。
如:
Expression.eqProperty(
"TUser.groupID",
"TGroup.id"
);
Expression.gtProperty 用于比较两个属性之间的值,对应SQL中的“field
> field”。
Expression.geProperty 用于比较两个属性之间的值,对应SQL中的“field
>= field”。
Expression.ltProperty 用于比较两个属性之间的值,对应SQL中的“field
< field”。
Expression.leProperty 用于比较两个属性之间的值,对应SQL中的“field
<= field”。
Expression.and and关系组合。
如:
Expression.and(
Expression.eq("name","Erica"),
Expression.eq(
"sex",
new Integer(1)
)
);
Expression.or or关系组合。
如:
Expression.or(
Expression.eq("name","Erica"),
Expression.eq("name","Emma")
);
Expression.sql 作为补充,本方法提供了原生SQL语法的支持。我
们可以通过这个方法直接通过SQL语句限定查询
条件。
下面的代码返回所有名称以“Erica”起始的记录:
Expression.sql(
“lower({alias}.name) like lower(?)”,
"Erica%",
Hibernate.STRING
);
其中的“{alias}”将由Hibernate在运行期使
用当前关联的POJO别名替换。
注重Expression 各方法中的属性名参数(如Express.eq中的第一个参数),这里
所谓属性名是POJO中对应实际库表字段的属性名(大小写敏感),而非库表中的实
际字段名称。
Criteria 高级特性
限定返回的记录范围
通过criteria. setFirstResult/setMaxResults 方法可以限制一次查询返回
的记录范围:
Criteria criteria = session.createCriteria(TUser.class);
//限定查询返回检索结果中,从第一百条结果开始的20条记录
criteria.setFirstResult(100);
criteria.setMaxResults(20);
对查询结果进行排序
//查询所有groupId=2的记录
//并分别按照姓名(顺序)和groupId(逆序)排序
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("groupId",new Integer(2)));
criteria.addOrder(Order.asc("name"));
criteria.addOrder(Order.desc("groupId"));
Criteria作为一种对象化的查询封装模式,不过由于Hibernate在实现过程中将精力
更加集中在HQL查询语言上,因此Criteria的功能实现还没做到尽善尽美(这点上,OJB
的Criteria 实现倒是值得借鉴),因此,在实际开发中,建议还是采用Hibernate 官
方推荐的查询封装模式:HQL。
Hibernate Query Language (HQL)
Criteria提供了更加符合面向对象编程模式的查询封装模式。不过,HQL(Hibernate
Query Language)提供了更加强大的功能,在官方开发手册中,也将HQL作为推荐的查询
模式。
相对Criteria,HQL提供了更接近传统SQL语句的查询语法,也提供了更全面的特性。
最简单的一个例子:
String hql = "from org.hibernate.sample.TUser";
Query query = session.createQuery(hql);
List userList = query.list();
上面的代码将取出TUser的所有对应记录。
假如我们需要取出名为“Erica”的用户的记录,类似SQL,我们可以通过SQL 语句加
以限定:
String hql =
"from org.hibernate.sample.TUser as user where user.name='Erica'";
Query query = session.createQuery(hql);
List userList = query.list();
其中我们新引入了两个子句“as”和“where”,as子句为类名创建了一个别名,而where
子句指定了限定条件。
HQL 子句本身大小写无关,但是其中出现的类名和属性名必须注重大小写区分。
关于HQL,Hibernate 官方开发手册中已经提供了极其详尽的说明和示例,详见
Hibernate官方开发手册(Chapter 11)。
数据关联
一对一关联
配置:
Hibernate中的一对一关联由“one-to-one”节点定义。
在我们的权限管理系统示例中,每个用户都从属于一个用户组。如用户“Erica”
从属于“System Admin”组,从用户的角度出发,这就是一个典型的(单向)一对
一关系。
每个用户对应一个组,这在我们的系统中反映为TUser 到 TGroup 的
one-to-one 关系。其中TUser 是主控方,TGroup是被动方。
one-to-one关系定义比较简单,只需在主控方加以定义。这里,我们的目标是
由TUser 对象获得其对应的TGroup 对象。因此TUser 对象是主控方,为了实现一
对一关系,我们在TUser 对象的映射文件TUser.hbm.xml 中加入one-to-one节
点,对TGroup对象进行一对一关联:
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<one-to-one
name="group"
class="org.hibernate.sample.TGroup"
cascade="none"
outer-join="auto"
constrained="false"
/>
……
</class>
</hibernate-mapping>
假如采用XDoclet,则对应的Tag如下:
/**
* @hibernate.class
* table="t_user"
* dynamic-update="true"
* dynamic-insert="true"
*
*/
public class TUser implements Serializable {
……
private TGroup group;
/**
* @hibernate.one-to-one
* name="group"
* cascade="none"
* class="org.hibernate.sample.TGroup"
* outer-join="auto"
* @return
*/
public TGroup getGroup() {
return group;
}
……
}
one-to-one 节点有以下属性:
属性 描述 类型 必须
name 映射属性 Text N
class 目标映射类。
注重要设为包含Package name的全路
径名称。
Text N
cascade 操作级联(cascade)关系。
可选值:
all : 所有情况下均进行级联操作。
none:所有情况下均不进行级联操作。
save-update:在执行save-update时
进行级联操作。
delete:在执行delete时进行级联操作。
级联(cascade)在Hibernate映射关
系中是个非常重要的概念。它指的是当主
控方执行操作时,关联对象(被动方)是
否同步执行同一操作。如对主控对象调用
save-update或delete方法时,是否同
时对关联对象(被动方)进行
Text N
save-update或delete。
这里,当用户(TUser)被更新或者删除
时,其所关联的组(TGroup)不应被修
改或者删除,因此,这里的级联关系设置
为none。
constrained 约束
表明主控表的主键上是否存在一个外键
(foreign key)对其进行约束。这个选
项关系到save、delete等方法的级联操
作顺序。
Bool N
outer-join 是否使用外联接。
true:总是使用outer-join
false:不使用outer-join
auto(默认) :假如关联对象没有采用
Proxy机制,则使用outer-join.
Text N
property-ref 关联类中用于与主控类相关联的属性名
称。
默认为关联类的主键属性名。
这里我们通过主键达成一对一的关联,所
以采用默认值即可。假如一对一的关联并
非建立在主键之间,则可通过此参数指定
关联属性。
Text N
access 属性值的读取方式。
可选项:
field
property(默认)
ClassName
Text N
一对多关联
一对多关系在系统实现中也很常见。典型的例子就是父亲与孩子的关系。 而在我
们现在的这个示例中,每个用户(TUser)都关联到多个地址(TAddress),如一个
用户可能拥有办公室地址、家庭地址等多个地址属性。这样,在系统中,就反应为一
个“一对多”关联。
一对多关系分为单向一对多关系和双向一对多关系。
单向一对多关系只需在“一”方进行配置,双向一对多关系需要在关联双方均加
以配置。
Ø 单向一对多关系
配置:
对于主控方(TUser):
TUser.hbm.xml:
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<set
name="addresses"
table="t_address"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
order-by="zipcode asc"
>
<key
column="user_id"
>
</key>
<one-to-many
class="org.hibernate.sample.TAddress"
/>
</set>
……
</class>
</hibernate-mapping>
对应的XDoclet Tag 如下:
/**
* @hibernate.collection-one-to-many
* class="org.hibernate.sample.TAddress"
*
* @hibernate.collection-key column="user_id"
*
* @hibernate.set
* name="addresses"
* table="t_address"
* inverse="false"
* cascade="all"
* lazy="false"
* sort=”unsorted”
* order-by="zipcode asc"
*
*/
public Set getAddresses() {
return addresses;
}
被动方(Taddress)的记录由Hibernate 负责读取,之后存放在主控方指定的
Collection类型属性中。
对于one-to-many 关联关系, 我们可以采用java.util.Set ( 或者
net.sf.hibernate.collection.Bag)类型的Collection,表现在XML 映射文件
中也就是<set>…</set>(或<bag>…</bag>)节点。关于Hibernate的Collection
实现,请参见Hibernate Reference.
one-to-many 节点有以下属性:
属性 描述 类型 必须
name 映射属性 Text Y
table 目标关联数据库表。 Text Y
lazy 是否采用延迟加载。
关于延迟加载,请参见后面相关章节。
Text N
inverse 用于标识双向关联中的被动方一端。
inverse=false的一方(主控方)负责
维护关联关系。
默认值: false
Bool N
cascade 操作级联(cascade)关系。
可选值:
all : 所有情况下均进行级联操作。
none:所有情况下均不进行级联操作。
save-update:在执行save-update时
进行级联操作。
delete:在执行delete时进行级联操作。
Text N
sort 排序类型。 Text N
可选值:
unsorted :不排序(默认)
natural :自然顺序(避免与order-by
搭配使用)
comparatorClass :指以某个实现了
java.util.Comparator接口的类作为排
序算法。
order-by 指定排序字段及其排序方式。
(JDK1.4以上版本有效)。
对应SQL中的order by子句。
避免与sort 的 “natural”模式同时使
用。
Text N
where 数据甄选条件,假如只需要处理库表中某
些特定数据的时候,可通过此选项设定结
果集限定条件。
Text N
outer-join 是否使用外联接。
true:总是使用outer-join
false:不使用outer-join
auto(默认) :假如关联对象没有采用
Proxy机制,则使用outer-join.
Text N
batch-size 采用延迟加载特性时(Lazy Loading)
一次读入的数据数量。
此处未采用延迟加载机制,因此此属性忽
略。
Int N
access 属性值的读取方式。
可选项:
field
property(默认)
ClassName
Text N
通过单向一对多关系进行关联相对简单,但是存在一个问题。由于是单向关联,
为了保持关联关系,我们只能通过主控方对被动方进行级联更新。且假如被关联方的
关联字段为“NOT NULL”,当Hibernate创建或者更新关联关系时,还可能出现约
束违例。
例如我们想为一个已有的用户“Erica”添加一个地址对象:
Transaction tx = session.beginTransaction();
TAddress addr = new TAddress();
addr.setTel("1123");
addr.setZipcode("233123");
addr.setAddress("Hongkong");
user.getAddresses().add(addr);
session.save(user);//通过主控对象级联更新
tx.commit();
为了完成这个操作,Hibernate会分两步(两条SQL)来完成新增t_address
记录的操作:
1. save(user)时:
insert into t_address (user_id, address, zipcode, tel)
values (null, "Hongkong", "233123", "1123")
2. tx.commit()时
update t_address set user_id=”1”, address="Hongkong",
zipcode="233123", tel="1123" where id=2
第一条SQL用于插入新的地址记录。
第二条SQL用于更新t_address,将user_id设置为其关联的user对象的id值。
问题就出在这里,数据库中,我们的t_address.user_id字段为“NOT NULL”
型,当Hibernate执行第一条语句创建t_address记录时,试图将user_id字段的
值设为null,于是引发了一个约束违例异常:
net.sf.hibernate.PropertyValueException: not-null property
references a null or transient value:
org.hibernate.sample.TAddress.userId
因为关联方向是单向,关联关系由TUser对象维持,而被关联的addr对象本身并
不知道自己与哪个TUser对象相关联,也就是说,addr对象本身并不知道user_id应
该设为什么数值。
因此,在保存addr时,只能先在关联字段插入一个空值。之后,再由TUser对象
将自身的id值赋予关联字段addr.user_id,这个赋值操作导致addr对象属性发生变
动,在事务提交时,hibernate会发现这一改变,并通过update sql将变动后的数
据保存到数据库。
第一个步骤中,企图向数据库的非空字段插入空值,因此导致了约束违例。
既然TUser对象是主控方,为什么就不能自动先设置好下面的TAddress对象的
关俩字段值再一次做Insert操作呢?莫名其妙?Ha,don’t ask me ,go to ask
Hibernate TeamJ。