Hibernate - 在实体集合中设置 null 会在事务提交时自动保留
Hibernate - Setting null in entity collection is automatically persisted at transaction commit
由于架构要求,我们不能将 Hibernate 实体用作 DTO,因此我们使用 Dozer 将这些实体转换为 POJO。
我们的典型服务如下所示:
@Transactional(readOnly=true)
@Override
public Task loadTask(int taskId){
TaskEntity taskE = taskDAO.load(taskId);
if (taskE != null){
taskE.setAttachments(null)
Task task = objectMapper.convert(taskE, Task.class);
return task;
}else{
return null;
}
}
如您所见,在将 TaskEntity 转换为 Task 之前,我们将附件设置为 null。这是因为附件是一个惰性集合,我们不想不必要地触发这些实体的加载。
在更新到 Spring 4.1.1 之前,这没有任何问题。但是,最近我们从 3.2.7 升级了 Spring,将 Hibernate 留在了 3.6.10。然后,当执行相同的代码时,我们注意到 Hibernate 在 loadTask 执行后执行了这条语句:
update TaskAttachment set taskId = NULL where id= ?
也就是说,由于在taskEntity.attachments中设置了null,Hibernate删除了TaskAttachment中的外键。
配置属性:
spring.transactionManager_class=org.springframework.transaction.jta.WebSphereUowTransactionManager
hibernate.transaction.manager_lookup_class=org.hibernate.transaction.WebSphereExtendedJTATransactionLookup
hibernate.current_session_context_class=jta
hibernate.transaction.factory_class=org.hibernate.transaction.JTATransactionFactory
jta.UserTransaction=java:comp/UserTransaction
会话出厂配置
<bean id="mainSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="jtaTransactionManager" ref="jtaTransactionManager" />
<property name="dataSource" ref="mainDataSource" />
<property name="packagesToScan" ref="packages-mainSessionFactory" />
<property name="hibernateProperties" ref="properties-mainSessionFactory" />
</bean>
我们唯一改变的与 ORM 相关的事情是我们停止使用 HibernateTemplate 以支持 SessionFactory.getCurrentSession()。
我们以前的 BaseDAO:
public abstract class BaseDAO<EntityType extends BaseEntity<IdType>, IdType extends Serializable> extends HibernateDaoSupport
public BaseDAO(HibernateTemplate hibernateTemplate, Class<EntityType> clazz){
super();
super.setHibernateTemplate(hibernateTemplate);
this.clazz= clazz;
}
public EntityType load(IdType id){
return getHibernateTemplate().get(clazz, id);
}
我们当前的 BaseDAO:
@SuppressWarnings("unchecked")
public abstract class BaseDAO<EntityType extends BaseEntity<IdType>, IdType extends Serializable> implements IBaseDAO<EntityType, IdType>{
private Class<EntityType> clazz;
private SessionFactory sessionFactory;
public BaseDAO(SessionFactory sessionFactory, Class<EntityType> clazz){
super();
this.clazz= clazz;
this.sessionFactory=sessionFactory;
}
public EntityType load(IdType id){
return (EntityType)getSession().get(clazz, id);
}
protected Session getSession(){
return sessionFactory.getCurrentSession();
}
更新: 这不是 Spring 版本相关的问题。我已经检查过使用 HibernateTemplate.get() 它不会保留空值,并且 SessionFactory.getCurrentSession().get() 确实如此,为什么?
而不是将您的集合设置为 null...更改实体中的级联选项,以便在保存父项时不保存子项。
@Transactional(readOnly=true)
告诉 Spring 该操作不会修改数据库,在这种情况下,它会将连接设置为只读,并且 Hibernate 不会更新实体。如果删除 readOnly=true
,您会发现即使使用 HibernateTemplate.get()
,更改也会保留。
如果您使用 SessionFactory.getCurrentSession()
,您将绕过 Spring 的初始化部分,该部分将会话设置为只读,因此更改会被保留。
然而,依靠 readOnly=true
来禁止更新并不是一个好的做法,因为它不一定得到所有 DB 和 ORM 的支持。最好的做法是使用 Session.evict()
分离实体。无论如何保留 readOnly=true
,因为如果 DB/ORM 支持它,那么数据库访问可以针对只读操作进行优化。
由于架构要求,我们不能将 Hibernate 实体用作 DTO,因此我们使用 Dozer 将这些实体转换为 POJO。
我们的典型服务如下所示:
@Transactional(readOnly=true)
@Override
public Task loadTask(int taskId){
TaskEntity taskE = taskDAO.load(taskId);
if (taskE != null){
taskE.setAttachments(null)
Task task = objectMapper.convert(taskE, Task.class);
return task;
}else{
return null;
}
}
如您所见,在将 TaskEntity 转换为 Task 之前,我们将附件设置为 null。这是因为附件是一个惰性集合,我们不想不必要地触发这些实体的加载。
在更新到 Spring 4.1.1 之前,这没有任何问题。但是,最近我们从 3.2.7 升级了 Spring,将 Hibernate 留在了 3.6.10。然后,当执行相同的代码时,我们注意到 Hibernate 在 loadTask 执行后执行了这条语句:
update TaskAttachment set taskId = NULL where id= ?
也就是说,由于在taskEntity.attachments中设置了null,Hibernate删除了TaskAttachment中的外键。
配置属性: spring.transactionManager_class=org.springframework.transaction.jta.WebSphereUowTransactionManager hibernate.transaction.manager_lookup_class=org.hibernate.transaction.WebSphereExtendedJTATransactionLookup hibernate.current_session_context_class=jta hibernate.transaction.factory_class=org.hibernate.transaction.JTATransactionFactory jta.UserTransaction=java:comp/UserTransaction
会话出厂配置
<bean id="mainSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="jtaTransactionManager" ref="jtaTransactionManager" />
<property name="dataSource" ref="mainDataSource" />
<property name="packagesToScan" ref="packages-mainSessionFactory" />
<property name="hibernateProperties" ref="properties-mainSessionFactory" />
</bean>
我们唯一改变的与 ORM 相关的事情是我们停止使用 HibernateTemplate 以支持 SessionFactory.getCurrentSession()。
我们以前的 BaseDAO:
public abstract class BaseDAO<EntityType extends BaseEntity<IdType>, IdType extends Serializable> extends HibernateDaoSupport
public BaseDAO(HibernateTemplate hibernateTemplate, Class<EntityType> clazz){
super();
super.setHibernateTemplate(hibernateTemplate);
this.clazz= clazz;
}
public EntityType load(IdType id){
return getHibernateTemplate().get(clazz, id);
}
我们当前的 BaseDAO:
@SuppressWarnings("unchecked")
public abstract class BaseDAO<EntityType extends BaseEntity<IdType>, IdType extends Serializable> implements IBaseDAO<EntityType, IdType>{
private Class<EntityType> clazz;
private SessionFactory sessionFactory;
public BaseDAO(SessionFactory sessionFactory, Class<EntityType> clazz){
super();
this.clazz= clazz;
this.sessionFactory=sessionFactory;
}
public EntityType load(IdType id){
return (EntityType)getSession().get(clazz, id);
}
protected Session getSession(){
return sessionFactory.getCurrentSession();
}
更新: 这不是 Spring 版本相关的问题。我已经检查过使用 HibernateTemplate.get() 它不会保留空值,并且 SessionFactory.getCurrentSession().get() 确实如此,为什么?
而不是将您的集合设置为 null...更改实体中的级联选项,以便在保存父项时不保存子项。
@Transactional(readOnly=true)
告诉 Spring 该操作不会修改数据库,在这种情况下,它会将连接设置为只读,并且 Hibernate 不会更新实体。如果删除 readOnly=true
,您会发现即使使用 HibernateTemplate.get()
,更改也会保留。
如果您使用 SessionFactory.getCurrentSession()
,您将绕过 Spring 的初始化部分,该部分将会话设置为只读,因此更改会被保留。
然而,依靠 readOnly=true
来禁止更新并不是一个好的做法,因为它不一定得到所有 DB 和 ORM 的支持。最好的做法是使用 Session.evict()
分离实体。无论如何保留 readOnly=true
,因为如果 DB/ORM 支持它,那么数据库访问可以针对只读操作进行优化。