我正在使用Spring和Hibernate(hibernate-core 3.3.1.GA),并且作为Web调用的结果,代码执行具有多个插入的事务。有时,其中一个插入失败,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
这个类包装了hibernate会话。
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;
...
}
LookupMapping类的Hibernate Mapping
<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 config
<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
错误。始终有两个此对象被持久化,并且在发生错误时,将创建与最后一个条目相同的重复ID。也就是说,如果第一个请求插入了ID 999
和1000
,并且如果下一个请求发生错误,则重复的ID将为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!
这是我到目前为止所有的信息,我希望我已经涵盖了相关的代码。任何帮助都感激不尽。如果我需要提供更多信息,请告诉我。
谢谢!
答案 0 :(得分:5)
问题在于Hibernate映射文件中定义的ID生成策略。
策略设置为increment
,这似乎仅在没有其他进程插入表时才有效。在我的情况下,似乎有时会有先前打开的会话,并且新请求最终同时插入到表中。
解决方案是将策略更改为native
,它使用底层数据库的策略生成ID。
<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>
答案 1 :(得分:3)
我同意@shyam的回复我会切换到一些序列生成器。
但也看看这段代码的和平:
User user;
// several lines to create User object
dao.save(user);
...
lookupService.saveUserConfigurations(user, userType, loginById);
在这种情况下,您将user
发送给未经管理的saveUserConfigurations
,并且saveUserConfigurations
内您可能会调用merge
方法。这将导致额外的insert语句。考虑将代码重构为:
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);
使用这样的结构,您将使用存储的实体(即由当前的hibernate会话管理)。并查看所有代码,并防止在存储后使用非托管实体。