Hibernate - Setting null in entity collection is automatically persisted at transaction commit

1k views Asked by At

Due to architecture requirements we can't use our Hibernate entities as DTOs, so we use Dozer to transform those entities to POJOs.

Our typical service looks like this:

@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;
    }
}

As you can see, before transforming TaskEntity to Task we set attachments to null. This is because attachments is a lazy collection and we don't want to unnecessarily trigger the loading of those entities.

Before updating to Spring 4.1.1 this worked without any problem. However, recently we upgraded Spring from 3.2.7, leaving Hibernate at 3.6.10. Then, when executing this same code we noticed that Hibernate was executing this statement after loadTask execution:

update TaskAttachment set taskId = NULL where id= ?

That is to say, because of setting null in taskEntity.attachments, Hibernate deletes the foreign key in TaskAttachment.

Configuration Properties: 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

Session Factory Config

<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>

The only ORM-related thing that we changed was that we stopped using HibernateTemplate in favour the SessionFactory.getCurrentSession().

Our former 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);
    }

Our current 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();
    }

UPDATE: It's not a Spring version related issue. I have checked that using HibernateTemplate.get() it doesn't persist the null, and with SessionFactory.getCurrentSession().get() it does, why?

2

There are 2 answers

2
EmirCalabuch On BEST ANSWER

@Transactional(readOnly=true) tells Spring that the operation will not be modifying the DB, in such a case it sets the connection to read-only and Hibernate will not update the entity. If you remove the readOnly=true you will see that even using HibernateTemplate.get() the change will be persisted.

If you use SessionFactory.getCurrentSession() you're circumventing Spring's initialization part that sets the session as readonly and therefore the changes get persisted.

Relying in readOnly=true to inhibit updates however is not a good practice because it is not necessarily supported by all DBs and ORMs. The best course of action is using Session.evict() to detach the entity. Anyway keep the readOnly=true because if the DB/ORM supports it then DB access could be optimized for read-only operations.

1
kondu On

Instead of setting your collection to null...change the cascade options in your entity so it doesn't save the child when saving the parent.