Ошибка гибернации сохраняется для объекта в методе @PostPersist - PullRequest
3 голосов
/ 06 января 2012

У меня странная ошибка двойной вставки в БД. У меня есть следующие классы:

  • TestEntity - сущность с методами @PrePersist и @PostPersist.
  • Аудитория - аудиторская организация
  • Набор данных - интерфейс DatasetBean
  • DatasetBean - bean-компонент без состояния, который реализует набор данных
  • DatasetFactory - экземпляр EJB набора данных (поиск)

Я поставил проблему в тесте junit (я использую встроенный Glassfish):

@Test
public void test() throws NamingException {
    Dataset<TestEntity> dataset = this.lookupBy(DatasetBean.class);
    Assert.assertNotNull(dataset);

    TestEntity t = new TestEntity();        
    t.setName(UUID.randomUUID().toString());

    dataset.insert(t);
    System.out.println("end");
}

Ход теста следующий:

  1. После получения объекта набора данных я пытаюсь вставить объект TestEntity

    @ Stateless @EJB (name = "...", beanInterface = Dataset.class) открытый класс DatasetBean реализует набор данных {

    @PersistenceContext(type = PersistenceContextType.TRANSACTION)
    private EntityManager entityManager;
    
    @Override
    public void insert(T entidade) {
        LOG.info("Inserting: " + entidade);
        entityManager.persist(entidade);
    }
    //...
    

    }

  2. Используя DatasetFactory, я пытаюсь вставить объект Auditing в метод @PostPersist объекта TestEntity

    открытый класс DatasetFactory { public static Dataset createDataset () { пытаться { return (набор данных) new InitialContext (). lookup ("..."); } catch (Exex ex) { бросить новое RuntimeException (ex); } } }

    @ Entity открытый класс TestEntity реализует MyEntity { @Я бы закрытый целочисленный идентификатор; личное строковое имя; // устанавливает и получает

    @PrePersist
    public void fillId() {
        if (getId() == null || getId() == 0) {
            Dataset d = DatasetFactory.createDataset();
            Integer i = (Integer) d.fetchJPQLFirstResult("SELECT MAX(te.id) FROM TestEntity te");
            if (i == null || i < 100) {
                setId(100);
            } else {
                setId(i + 1);
            }
        }
    }  
    
    @PostPersist
    public void audit() {
        Dataset<Auditing> dataset = DatasetFactory.createDataset();
        // dataset.getEntityManager().clear();
        Auditing auditing = new Auditing();
        auditing.setIdEntidade(String.valueOf(this.getId()));
        dataset.insert(auditing);
    }
    

    }

    @ Entity Общественный класс Auditoria реализует MyEntity { @Я бы @GeneratedValue (стратегии = GenerationType.IDENTITY) закрытый целочисленный идентификатор; приватная строка idEntity; // устанавливает и получает }

    открытый интерфейс MyEntity расширяет Serializable { Integer getId (); }

Вход

ИНФОРМАЦИЯ: внедренный был успешно развернут в 47.154 миллисекундах. PlainTextActionReporterSUCCESSDescription: развернуть AdminCommandApplication, развернутый с внедренным именем.

2012-01-06 02: 56: 54,826 [главная] ИНФОРМАЦИЯ com.joaosavio.model.db.DatasetBean (DatasetBean.java:30) - Вставка: TestEntity {id = null, name = ea5c2af4-0ca7-48a2- a82a-dbf582c570a9}

Hibernate: выберите max (testentity0_.id) в качестве col_0_0_ из TestEntity testentity0_

Hibernate: вставить в TestEntity (имя, идентификатор) значения (?,?)

2012-01-06 02: 56: 56,344 [главная] ИНФОРМАЦИЯ com.joaosavio.model.db.DatasetBean (DatasetBean.java:30) - Вставка: Auditoria {id = null, idEntidade = 100}

Hibernate: вставить в TestEntity (имя, идентификатор) значения (?,?)

2012-01-06 02: 56: 56,350 [main] WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper (SqlExceptionHelper.java:143) - Ошибка SQL: 2627, SQLState: 23000

2012-01-06 02: 56: 56,352 [main] ОШИБКА org.hibernate.engine.jdbc.spi.SqlExceptionHelper (SqlExceptionHelper.java:144) - Нарушение ограничения PRIMARY KEY 'PK_ TestEntity _76818E95. Невозможно вставить повторяющийся ключ в объект 'dbo.TestEntity'.

06.01.2012 02:56:56 com.sun.ejb.containers.BaseContainer postInvoke

ПРЕДУПРЕЖДЕНИЕ: системное исключение произошло во время вызова метода EJB DatasetBean public void com.joaosavio.model.db.DatasetBean.insert (java.lang.Object) javax.ejb.TransactionRolledbackLocalException: исключение, сгенерированное из bean-компонента ...

Причина: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Нарушение ограничения PRIMARY KEY 'PK_ TestEntity _76818E95'. Невозможно вставить повторяющийся ключ в объект 'dbo.TestEntity'. ...

Вызвано: org.hibernate.exception.ConstraintViolationException: Нарушение ограничения PRIMARY KEY 'PK_ TestEntity _76818E95'. Невозможно вставить дубликат ключа в объект "dbo.TestEntity".

Вопросы:

Полагаясь на то, что все работает нормально, если я очищаю диспетчер сущностей перед вставкой сущности Auditing (закомментированный код в методе @PostPersist в TestEntity), я считаю, что TestEntity застревает в транзакции.

Что я делаю не так ???

1 Ответ

10 голосов
/ 06 января 2012

Однажды я видел очень похожую проблему .... Вы должны ---

Будьте очень осторожны с @PostPersist!Действия компонента Hibernate сохраняются или сохраняются НЕ совпадают с операциями вставки в базу данных!

Проблема в вероятности , которая заключается в том, что вы предполагаете, что методы @PostPersist вызываются после того, как данные быливставлено .... Однако, это не всегда так!Методы PostPersist являются обратными вызовами, НО они не являются обратными вызовами из базы данных !!!Как вы знаете - hibernate, возможно, не совершил вашу транзакцию и полностью ее сбросил.если вы пытаетесь использовать PostPersist для координации барьеров между транзакциями в базе данных, вы совершаете ошибку.

Решение состоит в том, чтобы выполнить все вставки в одной правильно спланированной и управляемой транзакции с ключами и каскадами.разработано так, что hibernate сможет правильно организовать вставки для вас - или просто жестко закодировать хранимую процедуру, чтобы выполнить работу за вас.

Я думаю, вы можете объединять транзакции с состоянием и без сохранения состояниялогика здесь.

...