Hibernate不一致地生成重复的主键

shy*_*yam 3 java spring hibernate

我正在使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的部分抛出了异常.

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

LookupMapping

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

LookupMapping类的Hibernate映射

<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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

以下是日志中的行:

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/:?]
...
Run Code Online (Sandbox Code Playgroud)

我正在研究遗留代码库(因此无法轻松升级Hibernate),我编写的代码在LookupRepository类中(并且LookupService被调用UserService).

Duplicate entry同时,将持续发生错误LookupMapping的对象.始终有两个此对象被持久化,并且在发生错误时,将创建与最后一个条目相同的重复ID.也就是说,如果对于所述第一请求,标识9991000插入,并且如果下一个请求时发生的错误,则重复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!
Run Code Online (Sandbox Code Playgroud)

这是我到目前为止所有的信息,我希望我已经涵盖了相关的代码.任何帮助都感激不尽.如果我需要提供更多信息,请告诉我.

谢谢!

shy*_*yam 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>
Run Code Online (Sandbox Code Playgroud)