hibernate的错误 different object with the same identifier value was already associated with the session
1、今天在service层,循环保存导入的教室信息即Teacher对象,
然后调用Dao层的保存方法saveTeachers(Teacher teacher),用hibernate的
sessionFactory.getCurrentSession().save(teacher);
org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session
意思大概是说主键不唯一,即在事务的最后执行数据永久化时,session缓存里面有多个(>1)主键一样的对象。但,Teacher实体类对应的teacher表的id是自增长的主键,为什么还会有重复同一标识呢?
2、通过查找资料,慢慢理解hibernate的事务了,事务流程大概是这样的
应用使用session.save()保存对象,这个时候Session将这个对象放入entityEntries,用来标记对象已经和当前的会话建立了关联,由于应用对对对象做了保存的操作,Session还要在insertions中登记应用的这个插入行为(行为包括:对象引用、对象id、Session、持久化处理类)。即,其实调用session.save(class)之后,hibernate并不会立即提交数据库,而是先将要保存,更新,删除放进了缓存中(为什么要这样做呢?我想应该是,一方面数据永久化,实际上是将数据保存到硬盘等存储介质,学过操作系统的人都知道,外存的读取和存放的消耗是主存的几个数量级别的消耗,先放到缓存中,然后一次性写入到存储介质,减少了读写消耗;另一方面,应该是为了高效率实现事务滚回,众所皆知,事务具有原子性,要么都成功完成,要么都失败,那么如果一个事务要调用多个dao层实现数据的增删改查,如果hibernate是一条修改语句就立刻修改数据库的数据,一条删除语句就删除了,那如果删除语句删除失败,那又要去硬盘修改回去数据,那么这样会大大增大cpu的资源消耗,但如果这些一开始是放到缓存中进行标记,最后如果事务完成,则自动将缓存标记的操作写入到数据库,这样就能提高资源利用率了,这就是hibernate自带的一级缓存功能),等整个事务操作完成后,事务提示,需要将所有缓存flush入数据库,Session启动一个事务,并按照insert ,update,...,delete的顺序提交所有之前登记的操作(注意:所有insert执行完毕后才会执行update,这里的特殊处理也可能会将你的程序搞得一团遭,如需要控制操作的顺序,需要使用flush)
3、解释完事务流程,那么回答开始提出的问题,Teacher实体类对应的teacher表的id是自增长的主键,为什么还会有重复同一标识呢?因为每次调用sessionFactory.getCurrentSession().save(teacher)的时候,hibernate吧teacher实例对象保存到了缓存中,那么第二次循环保存第二个teacher实例时,因为在数据库设置的teacher主键是自增长的id,所以save时,我并没有给这两个teacher实例的id赋值,那么这两个实例在缓存中就没有唯一标识,这样就导致最后hibernate想要缓存中的实例写入到数据库时,没法区分这些实例了,也就报了这个错,我想,teacher表的自增长是只有在写入到数据库的时候,数据库会自动给赋值。
4、最后解决这个问题的方式是,给Teacher的实体类的主键加一个注解@GeneratedValue(strategy = GenerationType.IDENTITY) -->用IDENTITY告诉hibernate这个主键是自增长型,在调用save()的时候就在缓存中自动赋值
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
@GeneratedValue的使用方式是
JPA通用策略生成器
通过annotation来映射hibernate实体的,基于annotation的hibernate主键标识为@Id,
其生成规则由@GeneratedValue设定的.这里的@id和@GeneratedValue都是JPA的标准用法,
JPA提供四种标准用法,
JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO.
TABLE:使用一个特定的数据库表格来保存主键。
SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
IDENTITY:主键由数据库自动生成(主要是自动增长型)
AUTO:主键由程序控制
总结,一开始以为是跟网上的那种update的报错一样,使用session.evict(),session.save(),然后用session.flush(),的方法,但没用,因为我的问题的根本原因是没有主键,而update是因为实例的更换了,具体原因自己去看他们的博客。
解决问题的过程中,特别要感谢这几篇博客
1、session和getCurrentSession,openSession的区别
3、session.clear()和session.evict()的区别