在操作一对多关联时发生了一件怪事:一对多关系User(*)----Address(1), 配置文件如下:
Address.hbm.xml:
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="com.briup.third.many2one.bidirect">
<class name="Address" table="hedong_address_m2one" >
<id name="id" column="id" type="int" unsaved-value="-1">
<generator class="assigned"></generator>
</id>
<property name="city" column="city" type="string"/>
<property name="street" column="street" type="string"/>
<set name = "users" inverse="true" cascade="save-update">
<key column = "address_id"></key>
<one-to-many class = "User"/>
</set>
</class>
</hibernate-mapping>
Users.hbm.xml
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="com.briup.third.many2one.bidirect">
<class name="User" table="hedong_user_m2one">
<id name="id" column="id" type="int" unsaved-value="-1">
<generator class="assigned"></generator>
</id>
<property name="name" column="name" type="string"/>
<property name="age" column="age" type="int"/>
<many-to-one name="address" class = "Address" column = "address_id" ></many-to-one>
</class>
</hibernate-mapping>
进行级联保存的代码:
public static void main(String[] args) {
Configuration config = null;
SessionFactory sf = null;
Session session = null;
Transaction tran = null;
try{
config = new Configuration();
config.configure("com/briup/third/many2one/bidirect/hibernate.cfg.xml");
sf = config.buildSessionFactory();
session = sf.openSession();
tran = session.beginTransaction();
Address address = new Address();
address.setCity("ShangHai");
address.setStreet("GuoTai");
address.setId(100);
User user = new User();
user.setId(10);
user.setName("Tom");
user.setAge(22);
user.setAddress(address);
User user1 = new User();
user1.setId(20);
user1.setName("Susan");
user1.setAge(22);
user1.setAddress(address);
address.getUsers().add(user);
address.getUsers().add(user1);
session.save(address);
tran.commit();
System.out.println("Success!");
}catch(Exception e){
tran.rollback();
e.printStackTrace();
}finally{
if(session != null)
session.close();
if(sf != null)
sf.close();
}
}
最后发现打印出的SQL为:
Hibernate: insert into hedong_address_m2one (city, street, id) values (?, ?, ?)
Hibernate: update hedong_user_m2one set name=?, age=?, address_id=? where id=?
也就是说没有进行级联保存而是进行了级联更新,最后发现错在unsaved-value="-1",将其去掉即可。
到网上查得资料如下
当你显式的使用session.save()或者session.update()操作一个对象的时候,实际上是用不到unsaved-value的。某些情况下(父子表关联保存),当你在程序中并没有显式的使用save或者update一个持久对象,那么Hibernate需要判断被操作的对象究竟是一个已经持久化过的持久对象,是一个尚未被持久化过的内存临时对象。例如:
Session session = ...;
Transaction tx = ...;
Parent parent = (Parent); session.load(Parent.class, id);;
Child child = new Child();;
child.setParent(parent);;
child.setName("sun");;
parent.addChild(child);;
s.update(parent);;
s.flush();;
tx.commit();;
s.close();;
在上例中,程序并没有显式的session.save(child); 那么Hibernate需要知道child究竟是一个临时对象,还是已经在数据库中有的持久对象。如果child是一个新创建的临时对象(本例中就是这种情况),那么Hibernate应该自动产生session.save(child)这样的操作,如果child是已经在数据库中有的持久对象,那么Hibernate应该自动产生session.update(child)这样的操作。
因此我们需要暗示一下Hibernate,究竟child对象应该对它自动save还是update。在上例中,显然我们应该暗示Hibernate对child自动save,而不是自动update。那么Hibernate如何判断究竟对child是save还是update呢?它会取一下child的主键属性 child.getId() ,这里假设id是 java.lang.Integer类型的。如果取到的Id值和hbm映射文件中指定的unsave-value相等,那么Hibernate认为child是新的内存临时对象,发送save,如果不相等,那么Hibernate认为child是已经持久过的对象,发送update。
unsaved-value="null" (默认情况,适用于大多数对象类型主键 Integer/Long/String/...)
当Hibernate取一下child的Id,取出来的是null(在上例中肯定取出来的是null),和unsaved-value设定值相等,发送save(child)
当Hibernate取一下child的id,取出来的不是null,那么和unsaved-value设定值不相等,发送update(child)
例如下面的情况:
Session session = ...;
Transaction tx = ...;
Parent parent = (Parent); session.load(Parent.class, id);
Child child = (Child); session.load(Child.class, childId);
child.setParent(parent);
child.setName("sun");
parent.addChild(child);
s.update(parent);
s.flush();;
tx.commit();
s.close();
child已经在数据库中有了,是一个持久化的对象,不是新创建的,因此我们希望Hibernate发送update(child),在该例中,Hibernate取一下child.getId(),和unsave-value指定的null比对一下,发现不相等,那么发送update(child)。
BTW: parent对象不需要操心,因为程序显式的对parent有load操作和update的操作,不需要Hibernate自己来判断究竟是save还是update了。我们要注意的只是child对象的操作。另外unsaved-value是定义在Child类的主键属性中的。
<class name="Child" table="child">
<id column="id" name="id" type="integer" unsaved-value="null">
<generator class="identity"/>
</id>
...
</class>
如果主键属性不是对象型,而是基本类型,如int/long/double/...,那么你需要指定一个数值型的unsaved-value,例如:
unsaved-null="0"
在此提醒大家,很多人以为对主键属性定义为int/long,比定义为Integer/Long运行效率来得高,认为基本类型不需要进行对象的封装和解构操作,因此喜欢把主键定义为int/long的。但实际上,Hibernate内部总是把主键转换为对象型进行操作的,就算你定义为int/long型的,Hibernate内部也要进行一次对象构造操作,返回给你的时候,还要进行解构操作,效率可能反而低也说不定。因此大家一定要扭转一个观点,在Hibernate中,主键属性定义为基本类型,并不能够比定义为对象型效率来的高,而且也多了很多麻烦,因此建议大家使用对象型的Integer/Long定义主键。
unsaved-value="none"和 unsaved-value="any"
主主要用在主键属性不是通过Hibernate生成,而是程序自己setId()的时候。
在这里多说一句,强烈建议使用Hibernate的id generator,或者你可以自己扩展Hibernate的id generator,特别注意不要使用有实际含义的字段当做主键来用!例如用户类User,很多人喜欢用用户登陆名称做为主键,这是一个很不好的习惯,当用户类和其他实体类有关联关系的时候,万一你需要修改用户登陆名称,一改就需要改好几张表中的数据。偶合性太高,而如果你使用无业务意义的id generator,那么修改用户名称,就只修改user表就行了。
由这个问题引申出来,如果你严格按照这个原则来设计数据库,那么你基本上是用不到手工来setId()的,你用Hibernate的id generator就OK了。因此你也不需要了解当
unsaved-value="none"和 unsaved-value="any"
究竟有什么含义了。如果你非要用assigned不可,那么继续解释一下:
unsaved-value="none" 的时候,由于不论主键属性为任何值,都不可能为none,因此Hibernate总是对child对象发送update(child)
unsaved-value="any" 的时候,由于不论主键属性为任何值,都肯定为any,因此Hibernate总是对child对象发送save(child)
大多数情况下,你可以避免使用assigned,只有当你使用复合主键的时候不得不手工setId(),这时候需要你自己考虑究竟怎么设置unsaved-value了,根据你自己的需要来定。
BTW: Gavin King强烈不建议使用composite-id,强烈建议使用UserType。
因此,如果你在系统设计的时候,遵循如下原则:
1、使用Hibernate的id generator来生成无业务意义的主键,不使用有业务含义的字段做主键,不使用assigned。
2、使用对象类型(String/Integer/Long/...)来做主键,而不使用基础类型(int/long/...)做主键
3、不使用composite-id来处理复合主键的情况,而使用UserType来处理该种情况。
那么你永远用的是unsaved-value="null" ,不可能用到any/none/..了。
分享到:
相关推荐
4、使用测试的目的是确保解决方案在它所需要的环境下正常工作,其重点是从用户和运营人员的角度对解决方案进行测试。使用测试的类型包括:配置测试;兼容性测试;压力测试;性能测试;文档和帮助文件测试;可用性...
案例1 照片 智能搜索 授课人: 目录 目录 01 提出问题 02 预备知识 任务2——利用训练好的模型来辨识照片 03 04 05 任务1——训练目标人脸识别模型 解决方案 PART 01 PART 01 提出问题 随时人民生活水平的提高和手机...
2.4.4 解决打开文件时可能遇到的问题 2.5 写文件 2.5.1 fwrite()的参数 2.5.2 文件格式 2.6 关闭文件 2.7 读文件 2.7.1 以只读模式打开文件:fopen() 2.7.2 知道何时读完文件:feof() 2.7.3 每次读取一行数据:fgets...
2.4.4 解决打开文件时可能遇到的问题 2.5 写文件 2.5.1 fwrite()的参数 2.5.2 文件格式 2.6 关闭文件 2.7 读文件 2.7.1 以只读模式打开文件:fopen() 2.7.2 知道何时读完文件:feof() 2.7.3 每次读取一行...
2.4.4 解决打开文件时可能遇到的问题 2.5 写文件 2.5.1 fwrite()的参数 2.5.2 文件格式 2.6 关闭文件 2.7 读文件 2.7.1 以只读模式打开文件:fopen() 2.7.2 知道何时读完文件:feof() 2.7.3 每次读取一行...
同一解决方案中一个项目的xml注释在另一个项目中不能智能提示显示这些注释的解决方法.txt 图像读取.txt 无标题窗口的拖放.txt 下面的5行代码完成了从局域网中找出所有的机器.txt 显示DataGrid序号的一个适用的...
同一解决方案中一个项目的xml注释在另一个项目中不能智能提示显示这些注释的解决方法.txt 图像读取.txt 无标题窗口的拖放.txt 下面的5行代码完成了从局域网中找出所有的机器.txt 显示DataGrid序号的一个适用的...
实例004 根据需要创建所需解决方案 6 1.2 Visual Studio开发环境的使用 8 实例005 为程序设置版本和帮助信息 8 实例006 设置Windows应用程序启动窗体 9 实例007 统一窗体中控件的字体设置 10 实例008 通过“格式”...
使用Relations建立表之间的关系并却使用PagedDataSource类对DataList进行分页 通过作业,定时同步两个数据库 SQLSERVER高级注入技巧 利用反射实现ASP.NET控件和数据实体之间的双向绑定,并且在客户端自动验证输入的...
每个实例都是经过笔者精心筛选的,具有很强的实用性,其中一些实例是开发人员难于寻觅的解决方案。 本书两卷共计1200个例子,包括了开发中各个方面最常用的实例,是目前市场上实例最全面的开发类图书;本书实例来源...