`

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

    博客分类:
  • 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/..了。

分享到:
评论

相关推荐

    JQuery可编辑表格、横向纵向菜单、标签页、级联下拉框、窗口

    jQuery对话框(jQuery UI Dialog)提供了一种创建模态或非模态窗口的解决方案。通过设置宽度、高度、位置、按钮等属性,可以自定义窗口外观和行为。此外,窗口还支持拖动、缩放等交互,使得用户能够自由调整窗口大小...

    Hibernate 删除出现异常的解决方案.doc

    这个异常通常发生在试图删除的对象与其他对象之间存在级联关系(cascade),而Hibernate的级联策略不允许被删除的对象再次被保存。以下是三种常见的解决方法: 1. **删除Set方的级联(Cascade)** 当两个对象之间...

    制作调试过程及数据记录1

    在执行级联删除时,Hibernate会先查询相关联的数据,然后进行更新或删除操作。从给出的日志中可以看到,Hibernate执行了一系列SQL语句,包括查询部门和员工,然后尝试更新和删除。如果在更新或删除时出现问题,可能...

    基于AJAX在线考试系统的优化设计.pdf

    为了解决这些问题,本文提出了一种基于AJAX技术的优化设计方案,利用MAX技术结合ASP.NET平台,对考试系统中的登录、题目呈现、答案保存和试题管理四个模块进行优化改进。 首先,AJAX技术可以解决传统在线考试系统的...

    【推荐】法院执行指挥调度系统

    - **集中存储设计**:确保执行过程中产生的数据得到妥善保存。 - **语音对讲设计**:实现指挥中心与现场执行人员之间的实时通讯。 #### 四、管理平台 - **平台整体概述**: - 作为整个系统的管理中心,负责协调...

    数据库管理与应用-3期(KC004) 任务5-2 触发器教学设计.docx

    在训练过程中,教师会评估学生的操作,给出参考解决方案,展示和讨论学生的思路,以促进知识的巩固和技能的提升。课堂总结阶段,教师会再次强调触发器的独立性,Instead of触发器的独特性质以及级联触发器在数据关联...

    hibernate-release-5.2.10

    通过设置属性 cascade,可以控制实体间的操作是否级联,例如级联保存、更新、删除等。 七、缓存机制 Hibernate提供了一级缓存(Session级别)和二级缓存(SessionFactory级别)。一级缓存默认开启,二级缓存可配置...

    SSH框架分页~及增查

    对于增删改查(CRUD)操作,SSH框架提供了一套完整的解决方案: - **创建(Create)**:用户提交新数据,Action接收后调用Service,Service再调用DAO将数据保存到数据库。 - **读取(Read)**:用户请求数据,...

    ObjectGraphs.zip

    在这个项目中,"ObjectGraphs.sln"可能是一个Visual Studio解决方案文件,包含了整个项目的配置和引用。"ObjectGraphs"可能是项目的主程序或库,包含C#代码,用于定义对象模型和处理与数据库的交互。"Database...

    bibernate3.2

    另一个关键改进是支持级联操作,这意味着开发者可以定义实体之间的关联,并设置级联行为,如级联保存、更新或删除。这使得对象关系的管理变得更加简单,减少了手动操作数据库的需要。 此外,Hibernate 3.2在事务...

    java 遇到的各种异常

    - 使用级联保存(`cascade="save-update"`或`cascade="all"`等)来自动处理关联对象的保存操作。 **2. `org.springframework.orm.hibernate3.HibernateSystemException`: Don't change the reference to a ...

    hibernate3.2-中文帮助文档

    1. **Hibernate 概述**:文档首先介绍了Hibernate的核心概念,包括持久化、对象关系映射以及Hibernate如何作为解决方案来简化数据访问层的开发。这部分会解释ORM的基本原理,以及Hibernate如何将Java对象与数据库表...

    JPA 开发中遇到的错误

    这通常发生在级联保存的场景中,如果父实体尝试保存时,子实体还未被持久化。解决方案是在保存父实体之前先保存所有相关的子实体。 ### 8. javax.persistence.PersistenceException: Unable to configure ...

    高级软件架构师复习提纲

    4、使用测试的目的是确保解决方案在它所需要的环境下正常工作,其重点是从用户和运营人员的角度对解决方案进行测试。使用测试的类型包括:配置测试;兼容性测试;压力测试;性能测试;文档和帮助文件测试;可用性...

    专用触发器 使用教程 PDF

    SQL Server 7版本引入了新的触发器特性,支持企业级的商业解决方案。触发器可以与T-SQL语句(如ISQL)以及外部应用程序集成,例如,当向Sales表中添加新商品时,触发器会被执行。 在【部分内容】中,提到了触发器的...

    hibernate-distribution-3.6.10.Final-dist.zip

    在Hibernate 3.6.10.Final版本中,它提供了全面的对象持久化解决方案,包括数据访问接口、事务处理、查询语言等。 二、安装与配置 解压“hibernate-distribution-3.6.10.Final-dist.zip”文件后,我们可以看到...

    NHibernate示例程序

    8. **级联操作**:在映射文件中,可以定义对象之间的级联行为,如保存、更新或删除时,是否自动处理相关联的对象。 9. **事务管理**:NHibernate支持事务处理,确保数据的一致性和完整性。在Session中,可以通过...

    hibernate3

    7. **级联操作**:通过设置属性,可以实现对象之间的级联保存、更新、删除,简化了数据操作。 8. **延迟加载(Lazy Loading)**:Hibernate的懒加载机制能提高性能,只有在真正需要时才会加载关联的对象。 9. **...

    精通hibernate

    比如,@OneToMany和@ManyToOne关系可以设置cascade属性实现级联保存、更新和删除。Hibernate还内置了第一级缓存(Session缓存)和第二级缓存(SessionFactory缓存),提高性能。 六、实体关系 1. 一对一(OneToOne...

Global site tag (gtag.js) - Google Analytics