Я использую 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), и код, который я написал, относится к классу LookupRepository
(и LookupService
, который вызывается в 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!
Это вся информация, которая у меня есть, и я надеюсь, что также рассмотрел соответствующий код. Любая помощь будет высоко ценится. Дайте мне знать, если мне нужно дать больше информации.
Спасибо!