hibernate中一种导致a different object with the same identifier value was already associated with the session错误方式及解决方法
先将自己出现错误的全部代码都贴出来:
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.password">tiger</property> <property name="hibernate.connection.url">jdbc:mysql:///hibernate?useUnicode=true&characterEncoding=UTF-8</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">true</property> <property name="format_sql">true</property> <property name="hbm2ddl.auto">create</property> <mapping resource="com/third/Dao1/Grader1.hbm.xml"/> <mapping resource="com/third/Dao1/Students1.hbm.xml"/> </session-factory> </hibernate-configuration>
Students1.java
package com.third.Dao1; import java.io.Serializable; public class Students1 implements Serializable { private int sid; private String sname; private String sgender; public Students1() { } public int getSid() { return sid; } public void setSid(int sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public String getSgender() { return sgender; } public void setSgender(String sgender) { this.sgender = sgender; } }
Grader1.java
package com.third.Dao1; import java.io.Serializable; import java.util.HashSet; import java.util.Set; public class Grader1 implements Serializable { private int gid; private String gname; private Set<Students1> stuSet=new HashSet<Students1>(); public Grader1() { } public int getGid() { return gid; } public void setGid(int gid) { this.gid = gid; } public String getGname() { return gname; } public void setGname(String gname) { this.gname = gname; } public Set<Students1> getStuSet() { return stuSet; } public void setStuSet(Set<Students1> stuSet) { this.stuSet = stuSet; } }
由eclipse帮助生成的hbm.xml文件:
Students1.hbm.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 2017-3-1 20:42:49 by Hibernate Tools 3.5.0.Final --> <hibernate-mapping> <class name="com.third.Dao1.Students1" table="STUDENTS1"> <id name="sid" type="int"> <column name="SID" /> <generator class="assigned" /> </id> <property name="sname" type="java.lang.String"> <column name="SNAME" /> </property> <property name="sgender" type="java.lang.String"> <column name="SGENDER" /> </property> </class> </hibernate-mapping>
Grader1.cfg.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 2017-3-1 20:42:49 by Hibernate Tools 3.5.0.Final --> <hibernate-mapping> <class name="com.third.Dao1.Grader1" table="GRADER1"> <id name="gid" type="int"> <column name="GID" /> <generator class="increment" /> </id> <property name="gname" type="java.lang.String"> <column name="GNAME" /> </property> <set name="stuSet" inverse="false" table="STUDENTS1" lazy="true"> <key> <column name="GID" /> </key> <one-to-many class="com.third.Dao1.Students1" /> </set> </class> </hibernate-mapping>
测试文件:
Test.java
1 package com.third; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 6 import org.hibernate.Session; 7 import org.hibernate.SessionFactory; 8 import org.hibernate.Transaction; 9 import org.hibernate.cfg.Configuration; 10 import org.hibernate.service.ServiceRegistry; 11 import org.hibernate.service.ServiceRegistryBuilder; 12 import org.junit.After; 13 import org.junit.Before; 14 import org.junit.Test; 15 16 import com.third.Dao1.Grader1; 17 import com.third.Dao1.Students1; 18 19 /*import com.third.Dao.Grader; 20 import com.third.Dao.Students;*/ 21 public class Test1 { 22 23 private static SessionFactory sessionFactory; 24 private static Session session; 25 private static Transaction transaction; 26 @Before 27 public void init(){ 28 //创建配置对象,匹配读取hibernate.cfg.xml文件 29 Configuration config=new Configuration().configure(); 30 //获取服务注册对象(该对象中封装了hibernate.cfg.xml文件中的<properties>、<mapping>信息) 31 ServiceRegistry serviceRegistry=new ServiceRegistryBuilder() 32 .applySettings(config.getProperties()).buildServiceRegistry(); 33 //获取sessionFactory对象(该对象中通过传参serviceRegistry对象,获取了<mapping><properties>信息) 34 sessionFactory=config.buildSessionFactory(serviceRegistry); 35 } 55 56 @Test 57 public void test2(){ 58 //通过sessionFactory对象获取session对象 59 session=sessionFactory.openSession(); 60 //通过session对象开启事务,并返回Transaction对象 61 transaction=session.beginTransaction(); 62 63 //创建students和Grader对象 64 Students1 student2=new Students1(); 65 student2.setSname("小美"); 66 student2.setSgender("女"); 67 Students1 student3=new Students1(); 68 student3.setSname("小宏"); 69 student3.setSgender("女"); 70 71 Set<Students1> stuSet=new HashSet<Students1>(); 72 stuSet.add(student2); 73 stuSet.add(student3); 74 75 //实例Grader对象 76 Grader1 grader2=new Grader1(); 77 grader2.setGname("信息与计算科学二班"); 78 grader2.setStuSet(stuSet);; 79 80 81 session.save(student2); 82 83 session.save(student3); 84 session.save(grader2); 85 } 86 @After 87 public void destory(){ 88 //提交事务 89 transaction.commit(); 90 //关闭资源 91 if(session!=null){ 92 session.close(); 93 } 94 if(sessionFactory!=null){ 95 sessionFactory.close(); 96 } 97 } 98 }
下面是报错的报文:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.third.Dao1.Students1#0]
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:179)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:135)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:206)
at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:55)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:191)
at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:49)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90)
at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:764)
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:756)
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:752)
at com.third.Test1.test2(Test1.java:83)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
原因分析:
第一步:
报错报文中我们重点看加红的两行信息,第一行:
a different object with the same identifier value was already associated with the session: [com.third.Dao1.Students1#0]
大致的意思说:一个不同的Object对象和session:[com.third.Dao1.Students1#0]中已有的一个对象拥有着相同的唯一辨识符。我们仔细看这段代码,这段代码的主要作用是向Mysql数据库中创建两个关联表格,并且向表中添加一些信息。我们在分析一下这个报错是在我们编写的哪行代码执行不下去了之后,然后抛出异常。
第二步:
很明显:at com.third.Test1.test2(Test1.java:83)
这行错误报文指出我们是在Test1.java代码的83行抛出的异常,我们找到这行代码:session.save(student3);分析这行代码具体执行情况,在这之前只有session.save(students2);代码对session:[com.third.Dao1.Students1#0]操作,这样我们就将这两行代码结合在一起分析。之前说报错的原因是:session:[com.third.Dao1.Students1#0]中已有的对象的唯一标识符和新添加的对象相同,从而产生冲突。而session:[com.third.Dao1.Students1#0]中之前只有代码session.save(students2);对它进行了操作,也就是session:[com.third.Dao1.Students1#0]中只添加了studnets2这一个对象,这样要新添加的对象students3就和students2对象在唯一标识符上冲突。
第三步:
我们打开Navicat for MySQL软件,查看已经由eclipse创建的表格students1的表格结构(通过点击students1表格右键,选择设计表)
很明显表格students1记录的主键是SID,即在session:[com.third.Dao1.Students1#0]中的唯一表示符就是sid,那么我们看看表格中已有的students2的对象记录的主键:
我们能够看到students1表格中有一条SID为0的SNAME为小美的记录,我们可以知道session:[com.third.Dao1.Students1#0]中students2对象的唯一标识符sid为0,而students3对象唯一标识符和它相同冲突,这样students3对象的唯一表示符也将是sid为0。
第四步:
然后我们需要思考,为什么两个对象的唯一标识符都为0,这样我们就需要回到Test1.java代码中去分析,看看对于这个唯一标识符是如何设置的,即查看students2.setSid()和students3.setSid()函数的调用赋值情况。我们在查看完@Test代码段的代码发现,整段代码没有调用students2.setSid()和students3.setSid()函数,当时在设计这个程序时,想法是将主键SID交由MySQL数据库自行递增。所以自己写的代码段就没有对sid进行set赋值。但是MySQL数据库并没有完成主键SID的自动赋值(之前说students2和students3对象的唯一标识符sid都为0,在数据库表格中表现为主键SID都为0),这里就需要讨论数据库的主键的生成策略,而主键的生成策略是由.hbm.xml文件中指定的,这样我们就需要分析Students1.hbm.xml代码,其中控制主键生成策略的是<generator class="" />标签,我们很快找到了这个标签,查看这个标签主键生成策略的设置情况:
我们能看到这里设置的主键生成策略是assigned,而assigned适用于自然主键,由Java应用程序负责生成标识符,也就是说需要我们手动使用setSid()函数手动赋值。而我们并没有进行手动set赋值,而MySQL数据库在assirned主键生成策略下,如果不进行手动set赋值,其主键SID的值将会采取默认的值0,这就解释了唯一标识符sid都是0的冲突问题。
解决方法:(给出两种)
1、修改Students1.hbm.xml代码,其他代码不需要改变:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 2017-3-1 20:42:49 by Hibernate Tools 3.5.0.Final --> <hibernate-mapping> <class name="com.third.Dao1.Students1" table="STUDENTS1"> <id name="sid" type="int"> <column name="SID" /> <generator class="increment" /> </id> <property name="sname" type="java.lang.String"> <column name="SNAME" /> </property> <property name="sgender" type="java.lang.String"> <column name="SGENDER" /> </property> </class> </hibernate-mapping>
修改的代码是绿色背景的代码,将主键生成策略改成increment(适用于代理主键,由hibernate自动递增方式生成)或者改成native(适用于代理主键,根据底层数据库对自动生成标识符的方式自动选择,其中MySQL是自动递增方式生成)。
2、修改Test1.java代码,其他代码不需要改动。
1 package com.third; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 6 import org.hibernate.Session; 7 import org.hibernate.SessionFactory; 8 import org.hibernate.Transaction; 9 import org.hibernate.cfg.Configuration; 10 import org.hibernate.service.ServiceRegistry; 11 import org.hibernate.service.ServiceRegistryBuilder; 12 import org.junit.After; 13 import org.junit.Before; 14 import org.junit.Test; 15 16 import com.third.Dao1.Grader1; 17 import com.third.Dao1.Students1; 18 19 /*import com.third.Dao.Grader; 20 import com.third.Dao.Students;*/ 21 public class Test1 { 22 23 private static SessionFactory sessionFactory; 24 private static Session session; 25 private static Transaction transaction; 26 @Before 27 public void init(){ 28 //创建配置对象,匹配读取hibernate.cfg.xml文件 29 Configuration config=new Configuration().configure(); 30 //获取服务注册对象(该对象中封装了hibernate.cfg.xml文件中的<properties>、<mapping>信息) 31 ServiceRegistry serviceRegistry=new ServiceRegistryBuilder() 32 .applySettings(config.getProperties()).buildServiceRegistry(); 33 //获取sessionFactory对象(该对象中通过传参serviceRegistry对象,获取了<mapping><properties>信息) 34 sessionFactory=config.buildSessionFactory(serviceRegistry); 35 } 36 37 38 @Test 39 public void test2(){ 40 //通过sessionFactory对象获取session对象 41 session=sessionFactory.openSession(); 42 //通过session对象开启事务,并返回Transaction对象 43 transaction=session.beginTransaction(); 44 45 //创建students和Grader对象 46 Students1 student2=new Students1(); 47 student2.setSname("小美"); 48 student2.setSgender("女"); 49 student2.setSid(1); 50 Students1 student3=new Students1(); 51 student3.setSname("小宏"); 52 student3.setSgender("女"); 53 student3.setSid(2); 54 55 Set<Students1> stuSet=new HashSet<Students1>(); 56 stuSet.add(student2); 57 stuSet.add(student3); 58 59 //实例Grader对象 60 Grader1 grader2=new Grader1(); 61 grader2.setGname("信息与计算科学二班"); 62 grader2.setStuSet(stuSet);; 63 64 65 session.save(student2); 66 67 session.save(student3); 68 session.save(grader2); 69 } 70 @After 71 public void destory(){ 72 //提交事务 73 transaction.commit(); 74 //关闭资源 75 if(session!=null){ 76 session.close(); 77 } 78 if(sessionFactory!=null){ 79 sessionFactory.close(); 80 } 81 } 82 }
添加绿色背景的两段代码,手动的添加唯一标识符的值。
总结:
出现a different object with the same identifier value was already associated with the session的错误的原因是session中的对象的唯一标识符相同的冲突,其根本就是表现在数据库中的表格中的主键值相同的冲突或者其他唯一标识符冲突。解决这个冲突,我们必须知道,到底是哪个唯一标识符冲突,然后再去修改掉相同的标识符,让他们值不再相同,这样问题就解决了~