Hibernate непоследовательно генерирует дубликаты первичных ключей - PullRequest
0 голосов
/ 04 мая 2018

Я использую Spring и Hibernate (hibernate-core 3.3.1.GA), и в результате веб-вызова код выполняет транзакцию с несколькими вставками. Иногда одна из вставок завершается с ошибкой, говоря Hibernate 'Duplicate entry ... for key 'PRIMARY'. Мне не удалось определить какой-либо шаблон, когда это происходит - он может работать для 4–5 запросов, а затем не удается, затем работает при повторной попытке, а затем может завершиться неудачей при следующем запросе.

Ниже приведены соответствующие части кода:

Контроллер

@RequestMapping(value = "/users", method = RequestMethod.POST)
public @ResponseBody Map<Object, Object> save(<params>) throws IllegalArgumentException {
    ...
    try {
            map = userHelper.save(<parameters>);
    ...
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Исключение выдается в вышеуказанной части.

Метод UserHelper.save ()

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public HashMap<String, Object> save(<parameters>) throws NumberParseException, IllegalArgumentException, HibernateException {
    ....
    userService.save(<parameters>);
    return save;
}

UserService

HBDao dao;

@Autowired
public UserService(org.hibernate.SessionFactory sessionFactory) {
    dao = new HBDao(sessionFactory);
}
...
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public HashMap<String, Object> save(<parameters>) throws NumberParseException {
    ...
    User user;
    // several lines to create User object
    dao.save(user);
    ...
    lookupService.saveUserConfigurations(user, userType, loginById);
    ...
    return response;
}

HBDao

Этот класс переносит сеансы гибернации.

public HBDao(SessionFactory sf) {
    this.sessionFactory = sf;
}

private Session getSession() {
    sessionFactory.getCurrentSession();
}

public void save(Object instance) {
    try {
        getSession().saveOrUpdate(instance);
    } catch (RuntimeException re) {
        throw re;
    }
}

lookupService.saveUserConfigurations(user, userType, loginById) вызов результатов в следующих методах в LookupRepository классе, который будет выполнен:

LookupRepository

@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public LookupMapping save(LookupMapping configuration) {
    dao.save(configuration);
    return configuration;
}

public Collection<LookupMapping> saveAll(Collection<LookupMapping> configurations) {
    configurations.forEach(this::save);
    return configurations;
}

LookupMapping

@Entity
public class LookupMapping {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long configId;
    ...
}

Отображение Hibernate для класса LookupMapping

<hibernate-mapping package="com...configuration.domain">
    <class name="LookupMapping" table="lookup_mapping" mutable="false">
        <id column="id" name="configId" type="long">
            <generator class="increment"/>
        </id>
        ...
    </class>
</hibernate-mapping>

Конфигурация Hibernate

<hibernate-configuration>
    <session-factory name="sosFactory">
        <!-- Database connection settings -->
        ...

        <property name="connection.pool_size">2</property>

        <!-- SQL dialect -->
        <property name="dialect">com. ... .CustomDialect</property>

        <!-- Enable Hibernate's current session context -->
        <property name="current_session_context_class">org.hibernate.context.ManagedSessionContext</property>

        <!-- Disable the second-level cache -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>

        ...
</session-factory>
</hibernate-configuration>

Ниже приведены строки из журнала:

2018-05-04 10:24:51.321 7|13|60f566fa-4f85-11e8-ba9b-93dd5bbf4a00 ERROR [http-nio-8080-exec-1] org.hibernate.util.JDBCExceptionReporter - Duplicate entry '340932' for key 'PRIMARY'
2018-05-04 10:24:51.321 7|13|60f566fa-4f85-11e8-ba9b-93dd5bbf4a00 WARN [http-nio-8080-exec-1] org.hibernate.util.JDBCExceptionReporter - SQL Error: 1062, SQLState: 23000
2018-05-04 10:24:51.322 7|13|60f566fa-4f85-11e8-ba9b-93dd5bbf4a00 ERROR [http-nio-8080-exec-1] org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:94) ~[hibernate-core-3.3.1.GA.jar:3.3.1.GA]
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) ~[hibernate-core-3.3.1.GA.jar:3.3.1.GA]
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275) ~[hibernate-core-3.3.1.GA.jar:3.3.1.GA]
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266) ~[hibernate-core-3.3.1.GA.jar:3.3.1.GA]
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:167) ~[hibernate-core-3.3.1.GA.jar:3.3.1.GA]
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) [hibernate-core-3.3.1.GA.jar:3.3.1.GA]
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50) [hibernate-core-3.3.1.GA.jar:3.3.1.GA]
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027) [hibernate-core-3.3.1.GA.jar:3.3.1.GA]
    at com.arl.mg.helpers.UserHelper.save(UserHelper.java:329) [classes/:?]
...

Я работаю над устаревшей кодовой базой (поэтому не могу легко обновить Hibernate), и код, который я написал, относится к классу LookupRepositoryLookupService, который вызывается в UserService).

Ошибка Duplicate entry возникает при сохранении объектов LookupMapping. Всегда сохраняются два объекта, и при возникновении ошибки дублирующийся идентификатор создается так же, как и последняя запись. То есть, если для первого запроса были вставлены идентификаторы 999 и 1000, и если ошибка возникла для следующего запроса, дублированный идентификатор будет 1000 (а не 999).

Еще одна, возможно, важная вещь, которую стоит отметить: Hibernate показывает эту строку:

org.hibernate.jdbc.ConnectionManager [] - transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!

Это вся информация, которая у меня есть, и я надеюсь, что также рассмотрел соответствующий код. Любая помощь будет высоко ценится. Дайте мне знать, если мне нужно дать больше информации.

Спасибо!

Ответы [ 2 ]

0 голосов
/ 08 мая 2018

Я согласен с ответом @shyam. Я бы переключился на какой-нибудь генератор последовательности.

Но взгляните и на этот код:

User user;
// several lines to create User object
dao.save(user);
...
lookupService.saveUserConfigurations(user, userType, loginById);

В этом случае вы отправляете user на saveUserConfigurations, который не управляется, и в пределах saveUserConfigurations вы можете вызвать merge метод. Это вызовет дополнительный оператор вставки. Рассмотрите возможность рефакторинга вашего кода:

User user;
// several lines to create User object
// dao.save should return the stored value of user.
user = dao.save(user);
...
lookupService.saveUserConfigurations(user, userType, loginById);

С такими конструкциями вы будете использовать сохраненную сущность (то есть управляемую текущим сеансом гибернации). и посмотрите на весь ваш код и предотвратите использование неуправляемых объектов после их сохранения.

0 голосов
/ 08 мая 2018

Проблема была в стратегии генерации идентификатора, определенной в файле отображения Hibernate.

Стратегия была установлена ​​как increment, которая, кажется, работает только тогда, когда в таблицу не вставляются другие процессы. В моем случае кажется, что иногда были ранее открытые сессии, и новые запросы заканчивались одновременной вставкой в ​​таблицу.

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

<hibernate-mapping package="com...configuration.domain">
    <class name="LookupMapping" table="lookup_mapping" mutable="false">
        <id column="id" name="configId" type="long">
            <generator class="native"/>
        </id>
        ...
    </class>
</hibernate-mapping>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...