JPA(Hibernate)实体状态
一句话总结:通过JPA(Hibernate)对实体对象进行增删改查时,JPA(Hibernate)要维护即存储对象在Session即Hibernate一级缓存中,同时还要维护其状态,具体状态变化如下图所示:
通常我们无需关心JPA(Hibernate)的实体状态,但是碰到一些问题时需要对实体状态有所了解。
问题一:Cannot remove detached entity异常
通常我们会通过Spring配置自动事务,此时若在北向调用ServiceA查询出一个对象,这里查询也会有事务提交,那么这个对象其实处于detached状态,北向再调用ServiceB传递此对象进行删除,就会报错提示cannot remove detached entity异常。还有一个场景是批量提交场景,网上的示例通常在批量提交后会调用em.clear来释放内存,此时该对象也处于detached状态,再remove同样会报错。
northbound method() { Object obj = service.findObject(id); service.removeObject(obj); } service findObject method() { // Spring事务,自动提交commit return object; } service removeObject method(Object obj) { // 这里会报错 JpaEntityManager.remove(obj); }
问题二:了解实体状态后,我们知道事务自动提交后再操作对象会有detach异常,那么设计架构时就要确保每次REST请求时使用不同的Hibernate Session,若并发多次请求使用同一Session里缓存的对象,就得手动去维护对象的状态才能防止出现detach异常。
通常我们还会使用Spring IOC,注入单例对象,这会让我们觉得Hibernate Session是同一个,如下:
class Northbound { @Autowired Service service; } class Service { @PersistenceContext EntityManager em; }
但其实Spring已经帮我们隐含处理了,这里有点绕,Northbound注入Service时是单例的,每次调用的是同一个service对象。但Service注入EntityManager时,却会根据每个线程即每次REST请求注入新的EntityManager(其下封装Hibernate Session)。Spring会针对PersistenceContext注解做特殊处理,将EntityManager(Session)与线程绑定,存储在ThreadLocal中。在OSGI环境中,还会使用OSGI Coordinate机制。所以用断点调试去查看,每次请求的EntityManager对象都是不同的。
引用:
https://docs.oracle.com/cd/E16439_01/doc.1013/e13981/undejbs003.htm
https://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/jpa_overview_em_lifecycle.html
https://stackoverflow.com/questions/42074270/should-there-be-an-entitymanager-per-thread-in-spring-hibernate