2015-06-19 1 views
1

Из-за требований к архитектуре мы не можем использовать наши объекты Hibernate как DTO, поэтому мы используем Dozer для преобразования этих объектов в POJO.Hibernate - установка нуля в коллекции объектов автоматически сохраняется при фиксации транзакции

Наш типичный сервис выглядит следующим образом:

@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 к задаче мы устанавливаем вложения в нуль. Это связано с тем, что вложения представляют собой ленивую коллекцию, и мы не хотим излишне запускать загрузку этих объектов.

Перед обновлением до весны 4.1.1 это сработало без проблем. Однако недавно мы обновили Spring с 3.2.7, оставив Hibernate на 3.6.10. Затем, при выполнении этого же кода, который мы заметили, что Hibernate выполнял это заявление после выполнения loadTask:

update TaskAttachment set taskId = NULL where id= ? 

То есть, из-за установки нуля в taskEntity.attachments, 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: комп/UserTransaction

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

Единственная связанная с 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(); 
    } 

UPDATE: Это не весна версия. Связанный с этим вопрос Я проверил, что с использованием HibernateTemplate.get() он не сохраняет нуль, а с SessionFactory.getCurrentSession(). Get() он делает, почему?

+0

В чем проблема, с которой вы столкнулись? – kondu

+0

Извините, что я случайно не опубликовал до окончания, дайте мне 10 минут – codependent

+1

Это то, что я ожидал увидеть до вашего обновления. Вы меняете управляемый объект внутри транзакции. Значение hibernate будет отслеживать все изменения, и когда commit/end будет сохраняться в базе данных. По крайней мере, это будет так, если этот метод вызывается из другого метода @ @ Transactional. (Не могли бы вы также добавить из какой версии версию, которую вы обновляете)? –

ответ

1

@Transactional(readOnly=true) сообщает Spring, что операция не будет изменять БД, в этом случае он устанавливает соединение только с чтением, а Hibernate не будет обновлять объект. Если вы удалите readOnly=true, вы увидите, что даже с использованием HibernateTemplate.get() изменение будет сохранено.

Если вы используете SessionFactory.getCurrentSession(), вы обходите часть инициализации Spring, которая устанавливает сеанс как readonly, и поэтому изменения сохраняются.

Оказание поддержки в readOnly=true для предотвращения обновлений однако не является хорошей практикой, потому что это не обязательно поддерживается всеми БД и ОРМ. Лучший способ действий - использовать Session.evict() для отсоединения объекта. В любом случае сохраните readOnly=true, потому что если DB/ORM поддерживает его, тогда доступ к БД может быть оптимизирован для операций только для чтения.

+0

Ты мужчина! Удаление «readOnly = true» также вызвало поведение set-null с HibernateTemplate. Что вы предлагаете делать, используя метод Session.evict() в методе BaseDAO.load() в методе службы ... – codependent

+0

Мой совет - сохранить 'readOnly = true', потому что он дает другие оптимизации транзакции, но используйте 'Session.evict()' в 'loadTask()' сразу после вызова 'load()' (не внутри 'load()', потому что, вероятно, вы используете его для других целей). Также вернитесь к 'HibernateTemplate.get()', а не 'getCurrentSession()', если вам это действительно не нужно, хотя с 'evict()' оба использования должны работать. – EmirCalabuch

-1

Вместо того, чтобы установить свою коллекцию в null ... измените параметры каскада в свой объект, чтобы он не сохранял дочерний элемент при сохранении родителя.

+1

Они устанавливают коллекцию в null, чтобы Dozer не сверлялся в объекты коллекции при передаче значений в DTO, что вызовет ленивую нагрузку. Изменение типа каскада не помогает. – EmirCalabuch