Как включить Hibernate Interceptor, когда моя транзакция Hibernate управляется Spring? - PullRequest
6 голосов
/ 16 июня 2010

Если у меня есть отношение @OneToMany с @Cascade (CascadeType.SAVE_UPDATE) следующим образом

public class One {

    private Integer id;

    private List<Many> manyList = new ArrayList<Many>();

    @Id
    @GeneratedValue
    public Integer getId() {
        return this.id;
    }

    @OneToMany
    @JoinColumn(name="ONE_ID", updateable=false, nullable=false)
    @Cascade(CascadeType.SAVE_UPDATE)
    public List<Many> getManyList() {
        return this.manyList;
    }        

}

И многие класс

public class Many {

    private Integer id;

    /**
      * required no-arg constructor
      */ 
    public Many() {}

    public Many(Integer uniqueId) {
        this.id = uniqueId
    }

    /**
      * Without @GeneratedValue annotation
      * Hibernate will use assigned Strategy
      */ 
    @Id
    public Integer getId() {
        return this.id;
    }

}

Если у меня есть следующий сценарий

One one = new One();

/**
  * generateUniqueId method will Take care of assigning unique id for each Many instance
  */
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));
one.getManyList().add(new Many(generateUniqueId()));

И я звоню

sessionFactory.getCurrentSession().save(one);

Прежде чем продолжить

В соответствии с Переходное постоянство Справочная документация Hibernate, вы можете увидеть

Если родительский элемент передается для сохранения () , update () или saveOrUpdate (), все дочерние элементы передаются в saveOrUpdate ()

ок.Теперь давайте посмотрим, что такое Java Persistence с книгой Hibernate Рассказывает о методе saveOrUpdate

Hibernate запрашивает у таблицы MANY заданный идентификатор и , если он найден ,Hibernate обновляет строку . Если он не найден , требуется вставка новой строки .

Что может быть переведено в соответствии с

INSERT INTO ONE (ID) VALUES (?)

/**
  * I have four Many instances added To One instance
  * So four select-before-saving
  *
  * I DO NOT NEED select-before-saving 
  * Because i know i have a Fresh Transient instance
  */
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?
SELECT * FROM MANY WHERE MANY.ID = ?

INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?)

Любой обходной путь, чтобы избежать выбора перед сохранением ???Да, вы можете либо

  • Добавить столбец @Version (не применяется)
  • Реализовать метод isTransient, предоставляемый перехватчиком Hibernate ( Опция, которую я имею )

Таким образом, чтобы избежать выбора по умолчанию перед сохранением поведения по умолчанию при использовании этого вида каскадирования, я улучшил свой код, назначив перехватчик Hibernate сеансу Hibernate Чья транзакция управляется Spring .

Вот мой репозиторий

До (без перехватчика Hibernate): Отлично работает!

@Repository
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> {

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public void add(SomeEntity instance) {
        sessionFactory.getCurrentSession().save(instance);
    }

}

После (с Hibernate Inteceptor): что-то идет не так (SQL-запрос не выполняется - ни INSERT, ни SELECT-BEFORE-SAVING)

@Repository
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> {

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public void add(SomeEntity instance) {
        sessionFactory.openSession(new EmptyInterceptor() {
            /**
              * To avoid select-before-saving
              */
            @Override
            public Boolean isTransient(Object o) {
                return true;
            }
        }).save(instance);
    }

}

Мой вопрос: почему Springне сохраняется моя сущность и ее отношения при использовании Hibernate Interceptor, и что я должен сделать, чтобы работать нормально ???

Ответы [ 2 ]

3 голосов
/ 25 июня 2010

Spring поддерживает связь между текущим сеансом и текущей транзакцией (см. SessionFactoryUtils.java .) Поскольку уже существует сеанс, связанный с текущим вызовом метода DAO, вы должны использовать этот сеанс, или погрузитесь в мрачные детали связи нового сеанса с предыдущим контекстом транзакции. Это возможно, но со значительным риском, и определенно не рекомендуется. В спящем режиме, если у вас уже открыт сеанс, его следует использовать.

Сказав это, вы можете получить Spring для создания нового сеанса для вас и связать его с текущим контекстом транзакции. Используйте SessionFactoryUtils.getNewSession(SessionFactory, Interceptor). Если вы используете это вместо sessionFactory в hibernate, то это должно сохранить связь с транзакцией.

Первоначально вы можете закодировать это непосредственно в DAO. Когда он будет опробован и протестирован, и, как мы надеемся, сработает, вы сможете предпринять шаги по удалению кода Spring из вашей DAO, например, с помощью AOP, чтобы добавить рекомендации к методам add (), которые создают и очищают новый сеанс.

Другой альтернативой является использование глобального перехватчика. Несмотря на то, что он глобальный, вы можете настроить его локально. TransientInterceptor содержит threadLocal<Boolean>. Это флаг для текущего потока, указывающий, должен ли перехватчик возвращать true для isTransient. Вы устанавливаете его в true в начале метода add () и очищаете его в конце. Э.Г.

   class TransientInterceptor extends EntityInterceptor {
      ThreadLocal<Boolean> transientFlag = new ThreadLocal<Boolean)();
      public boolean isTransient() {
         return transientFlag.get()==Boolean.TRUE;
      }
      static public setTransient(boolean b) {
          transientFlag.set(b);
      }
   }

А потом в вашем DAO:

@Override
public void add(SomeEntity instance) {
   try {
       TransientInterceptor.set(true);
       sessionFactory.getCurrentSession().save(instance);
   }
   finally {
      TransientInterceptor.set(false);   
   }
}

Затем вы можете настроить TransientInterceptor в качестве глобального перехватчика в SessionFactory (например, LocalSessionFactoryBean.) Чтобы сделать это менее инвазивным, вы можете создать AOP вокруг рекомендации по применению этого поведения ко всем вашим методам добавления DAO, где это уместно.

0 голосов
/ 21 июня 2010

В методе after вы создаете новый сеанс, а не сбрасываете его, поэтому обновление не отправляется в базу данных.Это не имеет ничего общего с Spring, но это чистое поведение Hibernate.

Что вам, вероятно, нужно, это добавить перехватчик (entity) в sessionFactory, вероятно, настроенный с помощью Spring.Затем вы можете просто сохранить метод add () вашего репозитория, как и раньше.Смотри http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/LocalSessionFactoryBean.html#setEntityInterceptor%28org.hibernate.Interceptor%29

...