`

执行级联保存却进行级联更新的解决方案

    博客分类:
  • SSH
阅读更多

在操作一对多关联时发生了一件怪事:一对多关系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、使用测试的目的是确保解决方案在它所需要的环境下正常工作,其重点是从用户和运营人员的角度对解决方案进行测试。使用测试的类型包括:配置测试;兼容性测试;压力测试;性能测试;文档和帮助文件测试;可用性...

    人工智能基础与应用-人工智能人脸识别-人工智能机器也认识你-人工智能案例照片智能搜索.pdf

    案例1 照片 智能搜索 授课人: 目录 目录 01 提出问题 02 预备知识 任务2——利用训练好的模型来辨识照片 03 04 05 任务1——训练目标人脸识别模型 解决方案 PART 01 PART 01 提出问题 随时人民生活水平的提高和手机...

    PHP和MySQL WEB开发(第4版)

    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...

    PHP和MySQL Web开发第4版pdf以及源码

    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 每次读取一行...

    PHP和MySQL Web开发第4版

    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 每次读取一行...

    .net技术资料大全(语言规范 源码教程 学习笔记 技术资料 .net代码生成器)

    同一解决方案中一个项目的xml注释在另一个项目中不能智能提示显示这些注释的解决方法.txt 图像读取.txt 无标题窗口的拖放.txt 下面的5行代码完成了从局域网中找出所有的机器.txt 显示DataGrid序号的一个适用的...

    ASP升级.net资料大全(c#入门 语言规范 源码教程 学习笔记 技术资料 面试题 asp与.net代码生成器)

    同一解决方案中一个项目的xml注释在另一个项目中不能智能提示显示这些注释的解决方法.txt 图像读取.txt 无标题窗口的拖放.txt 下面的5行代码完成了从局域网中找出所有的机器.txt 显示DataGrid序号的一个适用的...

    C#开发实例大全(基础卷).软件开发技术联盟(带详细书签) PDF 下载

    实例004 根据需要创建所需解决方案 6 1.2 Visual Studio开发环境的使用 8 实例005 为程序设置版本和帮助信息 8 实例006 设置Windows应用程序启动窗体 9 实例007 统一窗体中控件的字体设置 10 实例008 通过“格式”...

    asp.net知识库

    使用Relations建立表之间的关系并却使用PagedDataSource类对DataList进行分页 通过作业,定时同步两个数据库 SQLSERVER高级注入技巧 利用反射实现ASP.NET控件和数据实体之间的双向绑定,并且在客户端自动验证输入的...

    Java开发实战1200例(第1卷).(清华出版.李钟尉.陈丹丹).part3

    每个实例都是经过笔者精心筛选的,具有很强的实用性,其中一些实例是开发人员难于寻觅的解决方案。 本书两卷共计1200个例子,包括了开发中各个方面最常用的实例,是目前市场上实例最全面的开发类图书;本书实例来源...

Global site tag (gtag.js) - Google Analytics