Hibernate / JPA - прослушиватель сущности не вызывается должным образом - PullRequest
6 голосов
/ 04 декабря 2011

Я пытаюсь использовать объекты EntityListener и методы обратного вызова в моем приложении Seam / Hibernate / JPA. Я использую управляемый Seam 2.2 контекст персистентности на JBoss 5.1 с PostgreSQL 9.1 на бэкэнде. У меня объявлена ​​следующая сущность:

@Entity(name = "TestEntity")
@EntityListeners(TestCallback.class)
@Table(name = "tbl_test")
public class TestEntity implements Serializable {

    private static final long serialVersionUID = 2016897066783042092L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "xxx")
    @SequenceGenerator(name = "xxx", sequenceName = "xxx")
    @Index(name = "xxx")
    @DocumentId
    private Long id = null;

    @Column
    private String test = null;
...
}

вместе со следующим классом обратного вызова EntityListener:

public class TestCallback {

    /**
     * Logger for this class
     */
    private Log logger = null;

    public TestCallback() {
        logger = Logging.getLog(TestCallback.class);
    }

    @PrePersist
    public void prePersist(TestEntity e) {
        logger.debug("prePersist(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("prePersist(TestEntity) - end"); //$NON-NLS-1$
    }

    @PostPersist
    public void postPersist(TestEntity e) {
        logger.debug("postPersist(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("postPersist(TestEntity) - end"); //$NON-NLS-1$
    }

    @PostLoad
    public void postLoad(TestEntity e) {
        logger.debug("postLoad(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("postLoad(TestEntity) - end"); //$NON-NLS-1$
    }

    @PreUpdate
    public void preUpdate(TestEntity e) {
        logger.debug("preUpdate(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("preUpdate(TestEntity) - end"); //$NON-NLS-1$
    }

    @PostUpdate
    public void postUpdate(TestEntity e) {
        logger.debug("postUpdate(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("postUpdate(TestEntity) - end"); //$NON-NLS-1$
    }

    @PreRemove
    public void preRemove(TestEntity e) {
        logger.debug("preRemove(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("preRemove(TestEntity) - end"); //$NON-NLS-1$
    }

    @PostRemove
    public void postRemove(TestEntity e) {
        logger.debug("postRemove(TestEntity) - start"); //$NON-NLS-1$

        logger.debug("postRemove(TestEntity) - end"); //$NON-NLS-1$
    }
}

Однако, когда я запускаю свой тест, я не вижу, чтобы все мои методы обратного вызова вызывались так, как я ожидал. Я провел тесты следующих сценариев:

  • Сохранение нового предмета
  • Обновление существующего элемента
  • Загрузка товара
  • Удаление элемента

Однако единственные обратные вызовы, которые я вижу при вызове:

  • @PrePersist
  • @PreRemove
  • @PostLoad
  • @PreUpdate

Остальные обратные вызовы выполняются не так, как ожидалось. Это нормальное поведение? Я просто неправильно это понимаю? Это как-то связано с тем, как Seam управляет транзакциями? Или я просто что-то не так делаю?

Буду признателен за любую помощь.

РЕДАКТИРОВАТЬ: В соответствии с просьбой, вот точный код, который я звоню, и вывод, который я получаю:

Тест 1:

public void runTest() {
    logger.debug("runTest() - start"); //$NON-NLS-1$

    TestEntity e = new TestEntity();
    e.setTest("XXX");

    this.entityManager.persist(e);
    this.entityManager.flush();
    this.entityManager.clear();

    logger.debug("runTest() - end"); //$NON-NLS-1$
}

Выход 1:

12:27:56,307 INFO  [STDOUT] 29735 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest() - start
12:27:56,312 INFO  [STDOUT] 29740 DEBUG myapp.test.entities.TestCallback  -  - prePersist(TestEntity) - start
12:27:56,312 INFO  [STDOUT] 29740 DEBUG myapp.test.entities.TestCallback  -  - prePersist(TestEntity) - end
12:27:56,347 INFO  [STDOUT] 29775 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest() - end

Тест 2:

public void runTest2() {
        logger.debug("runTest2() - start"); //$NON-NLS-1$

        String sql = "SELECT DISTINCT t FROM TestEntity t";
        Query q = this.entityManager.createQuery(sql);

        List<TestEntity> l = q.getResultList();
        for (int i = 0; i < l.size(); i++) {
            String x = l.get(i).getTest();
            logger.debug("runTest2() - String x=" + x); //$NON-NLS-1$
        }

        logger.debug("runTest2() - end"); //$NON-NLS-1$
    }

Выход 2:

12:28:36,964 INFO  [STDOUT] 70392 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest2() - start
12:28:36,982 INFO  [STDOUT] 70410 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - start
12:28:36,982 INFO  [STDOUT] 70410 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - end
12:28:36,982 INFO  [STDOUT] 70410 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest2() - String x=XXX
12:28:36,983 INFO  [STDOUT] 70411 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest2() - end

Тест 3:

public void runTest3() {
        logger.debug("runTest3() - start"); //$NON-NLS-1$

        String sql = "SELECT DISTINCT t FROM TestEntity t";
        Query q = this.entityManager.createQuery(sql);

        List<TestEntity> l = q.getResultList();
        for (int i = 0; i < l.size(); i++) {
            l.get(i).setTest("YYY" + System.currentTimeMillis());
            this.entityManager.persist(l.get(i));
        }
        this.entityManager.flush();
        this.entityManager.clear();

        Random rand = new SecureRandom();

        q = this.entityManager.createQuery(sql);
        l = q.getResultList();
        for (int i = 0; i < l.size(); i++) {
            this.entityManager.remove(l.get(i));
        }

        this.entityManager.flush();
        this.entityManager.clear();

        logger.debug("runTest3() - end"); //$NON-NLS-1$
    }

Выход 3:

12:30:00,404 INFO  [STDOUT] 153832 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest3() - start
12:30:00,407 INFO  [STDOUT] 153835 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - start
12:30:00,407 INFO  [STDOUT] 153835 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - end
12:30:00,408 INFO  [STDOUT] 153836 DEBUG myapp.test.entities.TestCallback  -  - preUpdate(TestEntity) - start
12:30:00,408 INFO  [STDOUT] 153836 DEBUG myapp.test.entities.TestCallback  -  - preUpdate(TestEntity) - end
12:30:00,410 INFO  [STDOUT] 153838 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - start
12:30:00,411 INFO  [STDOUT] 153839 DEBUG myapp.test.entities.TestCallback  -  - postLoad(TestEntity) - end
12:30:00,414 INFO  [STDOUT] 153842 DEBUG myapp.test.entities.TestCallback  -  - preRemove(TestEntity) - start
12:30:00,414 INFO  [STDOUT] 153842 DEBUG myapp.test.entities.TestCallback  -  - preRemove(TestEntity) - end
12:30:00,453 INFO  [STDOUT] 153881 DEBUG myapp.test.web.actions.test.TestAction  -  - runTest3() - end

Ответы [ 3 ]

8 голосов
/ 10 декабря 2011

Извините, если я даю неправильный ответ ... Я не знаю, Шов.

Но в своей теме вы говорите "Hibernate / JPA", что неясно. Вы сохраняете сущности, используя Session из SessionFactory или EntityManager из EntityManagerFactory?

Существует большая разница, потому что если вы используете Seam с SessionFactory, большая разница в том, что по умолчанию прослушиватели JPA (которые запускают ваши аннотированные обратные вызовы) не регистрируются по умолчанию, в то время как они с EntityManagerFactory. Таким образом, возможно, что вы используете SessionFactory и что кто-то еще в вашем проекте, который настроил эту фабрику сеансов, зарегистрировал только подмножество всех прослушивателей обратного вызова JPA.

См: http://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/configuration.html#d0e865


Edit: Хорошо, извините, вы используете EntityManager ...

но, возможно, было бы неплохо попытаться получить этот SessionFactory за EntityManagerFactory и посмотреть, какие прослушиватели событий зарегистрированы. Вы одни в этом заявлении? Если кто-то попытался зарегистрировать пользовательский / устаревший обработчик событий или что-то в этом роде, он мог переопределить обработчик событий JPA.

Это может быть достигнуто чем-то вроде этого:

EntityManager em = ...
Session session = (Session)em.getDelegage()
SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl)session.getSessionFactory();
EventListeners el = sessionFactoryImpl.getEventListeners()

И тогда вы можете посмотреть, что внутри, например, по вашей проблеме вы можете сравнить:

el.getPreLoadEventListeners()
el.getPreDeleteEventListeners()

И помните, что «поведение по умолчанию»: http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html_single/#d0e865

Похоже, что можно легко переопределить прослушиватели JPA по умолчанию, вот что: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/listeners.html

Было бы неплохо, если бы вы показали нам свою настойчивость. Xml

3 голосов
/ 09 декабря 2011

Без реальных тестов, которые вы выполняли, способа, которым вы их выполняли, и результатов, которые вы получили (от слушателя), трудно сказать что-нибудь умное, однако используя MySQL со следующей таблицей и данными

CREATE TABLE `test` (
  `id` int(11)
  `data` char(1),
  PRIMARY KEY (`id`)
);
+----+------+
| id | data |
+----+------+
|  1 | a    |
+----+------+

Я провел этот тест

EntityManagerFactory emf = ...
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();

// persisting new item
Test b = new Test();
b.id = 2;
b.data = 'b';

tx.begin();
em.persist(b);
tx.commit();

// update existing item
b.data = 'a';

tx.begin();
em.merge(b);
tx.commit();

// load existing
Test a = em.find(Test.class, 1);

// remove existing
tx.begin();
em.remove(b);
tx.commit();

em.close();
emf.close();

и получил следующий вывод

prePersist(2: b)
postPersist(2: b)
preUpdate(2: a)
postUpdate(2: a)
postLoad(1: a)
preRemove(2: a)
postRemove(2: a)

(я реализовал toString() в Test, чтобы он выдавал id + ": " + data, и я сократил количество записей в моем классе прослушивателя до System.out.format("preXxx(%s)\n", e), чтобы его было лучше читать.)

Пожалуйста, посмотрите, работает ли этот в вашей среде для начинающих, чтобы мы могли сузить проблему, но, по крайней мере, опубликовать некоторые из ваших входов / выходов.


Я пытался загрузить простое тестовое приложение JBoss / Seam для себя, чтобы запустить тест, однако я не получаю Seam & hellip; совсем. Во всяком случае, я посмотрел на ваши примеры, и вот что я думаю, что ваша проблема.

Испытание 1 / Ouput 1

Я предполагаю, что ваш runTest() метод запускает управляемый сеанс персистентности или что-то в этом роде, поэтому перед вызовом метода entityManager.getTransaction().begin() вызывается Seam, а когда метод существует, вызывается entityManager.getTransaction().commit(). Вот мое мнение о том, что, кажется, происходит в вашем сценарии.

// transaction begin
// persist - @PrePersist called
this.entityManager.persist(e);
// flush - @PostPersist should be called
this.entityManager.flush();
// clear - the context is cleared
this.entityManager.clear();
// transaction end

Я тестировал этот сценарий, однако использование MySQL с ручным управлением сохранением @PostPersist называется.

Я пытался установить entityManager.setFlushMode(...) на FlushModeType.AUTO и FlushModeType.COMMIT, но это ничего не меняет. Однако в документации Seam есть третье перечисление, FlushModeType.MANUAL, которое не входит в спецификацию JPA и является специфичным для Seam. Он должен быть подан к аннотации @Begin (эта аннотация, вероятно, указывает, что аннотированный метод запускает транзакцию под рукой).

Я совершенно уверен, что в вашем случае flush() ничего не фиксирует в базе данных, пока транзакция не завершится (методы запускают out ). Если вы удалите вызов метода clear(), будет вызван @PostPersist.

Тест 2 / Выход 2

Мне кажется, это нормально!

Тест 3 / Выход 3

Полагаю, ваша проблема в том, что @PrePersist и @PostPersist вообще не вызывают, но вы звоните persist(). Поскольку вы используете find(), возвращенные экземпляры уже будут управляться. Вызов persist() на них ничего не делает. Однако изменение их состояния и вызов flush() вызывает @PreUpdate. @PostUpdate не вызывается по той же причине @PostPersist не вызывается в первом тесте: вы очищаете контекст постоянства, прежде чем что-либо может распространиться в базу данных.

Заключение

Ваша проблема сводится к тому, что вы clear() сохраняете свой контекст постоянства, прежде чем что-либо будет зафиксировано в базе данных.

Я предлагаю вам сделать действительно простых тестов и попробовать поиграть с настройками FlushModeType, которые я упомянул. Обновите свою версию Hibernate. Я использую последнюю версию из серии 3.x.x (3.8.6.Final). Кстати, вы точно не поделились с нами своей версией Hibernate, но это может иметь большое значение!

2 голосов
/ 09 декабря 2011

Это не нормальное поведение. Я работаю над проектом, использующим шов 2.2.2 с контекстом управляемости шва, jpa / hibernate, jboss 5.1 и mysql, и все обратные вызовы вызываются корректно. Возможно, в вашем тестовом примере есть ошибка.

...