Исключение при автоматическом сбросе в конце транзакции

Я заполняю базу данных записями, которые следует заменить, если они уже есть. Для этого сначала я проверяю, присутствует ли уже запись с помощью find (которая заключена в транзакцию только для чтения), затем, если возвращаемое значение равно null, я сохраняю вновь созданную запись (заключенную в транзакцию), если возвращаемое значение является существующей записью, я сначала удаляю ее (заключенную в транзакцию), а затем добавляю новую запись. Помимо того факта, что это много транзакций, которые могут быть выполнены внутри одной транзакции, мне интересно, почему я получаю исключение, если запись уже существует. Я проверил режим очистки, и он установлен на AUTO, поэтому он должен сбрасываться в конце транзакции. Это стек исключения:

Exception in thread "main" java.lang.NullPointerException
    at org.hibernate.engine.internal.NaturalIdXrefDelegate.validateNaturalId(NaturalIdXrefDelegate.java:175)
    at org.hibernate.engine.internal.NaturalIdXrefDelegate.cacheNaturalIdCrossReference(NaturalIdXrefDelegate.java:85)
    at org.hibernate.engine.internal.StatefulPersistenceContext$1.cacheNaturalIdCrossReferenceFromLoad(StatefulPersistenceContext.java:1817)
    at org.hibernate.engine.internal.StatefulPersistenceContext.getNaturalIdSnapshot(StatefulPersistenceContext.java:340)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkNaturalId(DefaultFlushEntityEventListener.java:110)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:199)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:156)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1213)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:402)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
    at org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:480)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:392)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy31.save(Unknown Source)
    at controller.FileParserCore.update(FileParserCore.java:39)
    at controller.Core.runCore(Core.java:122)
    at controller.Core.staticRunCore(Core.java:74)
    at controller.Core.main(Core.java:33)

Я использую Hibernate и Spring.

Это фрагмент кода, который не работает, если запись уже существует:

Entry entry = getNewEntry();
if (entryDAO.find(entry.getName()) != null) {
    entryDAO.remove(entry.getName());
}
entryDAO.save(entry);

Это соответствующий DAO:

@Repository
public class EntryDAO extends GenericDAO<Entry> implements IEntryDAO {
    private static final Logger log = LoggerFactory.getLogger(EntryDAO.class);

    @Override
    @Transactional
    public void save(Entry entry) {
        makePersistent(entry);
        log.info("Saved: {}", entry);
    }

    @Override
    @Transactional
    public void remove(String name) {
        Entry entry = find(name);
        if (entry != null) {
            makeTransient(entry);
            log.info("Removed: {}", entry);
        } else {
            log.warn("Could not remove: {}, entry not found", name);
        }
    }

    /**
     * find an entry by its name and return it
     */
    @Override
    @Transactional(readOnly = true)
    public Entry find(String name) {
        return (Entry) createCriteria(Restrictions.eq("name",name)).uniqueResult();
    }
}

А это объект домена Entry:

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = false, of = { "name" })
public class Entry extends DomainObject implements Serializable {
    @NaturalId
    @NotEmpty
    @Length(max = 16)
    @Index(name = "ix_name")
    private String name;
}

Это результат до исключения:

INFO  2013-03-01 10:22:17,899 main - Removed: someEntry
INFO  2013-03-01 10:22:18,117 main - Saved: someEntry

Когда я проверяю базу данных, старая запись удаляется, но новая запись не сохраняется. Кроме того, идентификаторы старой записи и новой записи разные, только имена совпадают. Из трассировки стека кажется, что это как-то связано с naturalID, может быть, из-за того, что очистка не завершила удаление старой записи, имя новой записи конфликтует с этим? Но не должна ли старая запись уже давно исчезнуть, когда она входит в метод save ()?

ОБНОВЛЕНИЕ: я также проверил, будет ли работать размещение всего в одной транзакции, но, конечно, он начал плакать, когда я попытался удалить запись и сразу после этого добавить запись с тем же NaturalID (их имя). Итак, я подумал: давайте поместим между ними getCurrentSession (). Flush (). Результат: то же исключение, что и выше.

ОБНОВЛЕНИЕ: вот класс DomainObject:

@MappedSuperclass
@EqualsAndHashCode
public abstract class DomainObject implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long    id;

    public Long getId() {
        return id;
    }

    @Override
    public abstract String toString();
}

person Erik Stens    schedule 01.03.2013    source источник
comment
не могли бы вы также опубликовать код для входа?   -  person radai    schedule 01.03.2013
comment
Похоже, что для DomainObject неправильно установлен @Id или отсутствует @MappedSuperclas. Не могли бы вы опубликовать DomainObject?   -  person n1ckolas    schedule 01.03.2013


Ответы (2)


Извините, но я уже решил это. Это было связано со случайным каскадным удалением в отношении «многие ко многим», не упомянутым здесь (я не упоминал об этом, потому что думал, что это не связано с проблемой). Оказывается, он удалял все одним единственным удалением, потому что несколько записей были связаны друг с другом посредством каскадирования «многие ко многим». После снятия каскада все проблемы были решены.

person Erik Stens    schedule 11.03.2013

В моем случае у меня было вложение операций гибернации, вызванных EntityListener. Во время операции сохранения некоторого объекта будет вызываться перехватчик аудита (слушатель), который будет читать данные из БД, нарушая сеанс.

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

person Vituel    schedule 12.08.2014