最近在做项目的时候遇到一个问题,客户在使用Hibernate往数据库插入记录时总是遇到这样的错误信息:
Caused by: net.sf.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at net.sf.hibernate.exception.ErrorCodeConverter.convert(ErrorCodeConverter.java:73)
at net.sf.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:29)
at net.sf.hibernate.impl.BatcherImpl.convert(BatcherImpl.java:328)
at net.sf.hibernate.impl.BatcherImpl.executeBatch(BatcherImpl.java:135)
at net.sf.hibernate.impl.SessionImpl.executeAll(SessionImpl.java:2438)
at net.sf.hibernate.impl.SessionImpl.execute(SessionImpl.java:2396)
at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2261)
... 3 more
Caused by: java.sql.BatchUpdateException: Duplicate entry '42808-0' for key 1
at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:894)
at org.apache.commons.dbcp.DelegatingStatement.executeBatch(DelegatingStatement.java:294)
at net.sf.hibernate.impl.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:54)
at net.sf.hibernate.impl.BatcherImpl.executeBatch(BatcherImpl.java:128)
... 7 more
而这个问题在本地测试时并未出现,开始时猜测是客户的数据库结构与本地的数据库结构不同造成的,可是经过比对,客户的数据库结构和环境与本地完全一样。
接下来分析数据库中的数据库中的数据,发现在几个表中存在垃圾数据。为了说明情况,我们假定A表中有垃圾数据,A与B、C表通过外键关联(用的是MySQL4.0,我自己不太了解,似乎没有5.0来的那么严格),然而,有意思的是在B、C表中并没有相关的数据,而且A中相关记录的PK明显大于B、C表中的最大PK。
由于B、C两个表的主键由Hibernate生成,采用的生成机制是identity,因此,主键的生成实际上是依赖于MySQL数据库本身。对于MySQL而言,数据库本身会维护本身的index,如果数据库的当前的最大的index是2000,即使删除了后一千条数据,新生成的数据也应该从2001开始。反过来,既然在A表中存在大于B、C表中最大PK的记录,那么说明A中这些个对应记录的至少曾经在B、C表存在过,那么在重新生成时,不应该出现重复的记录出现才对啊!
继续检查代码,发现A表是在B、C生成的时候自动生成的,但是删除的时候却忘了删除,但是即使这样,也不该出现先前提到的情况啊!
询问客户的数据库的情况,才知道,客户的数据库是新装了之后用以前的SQL备份文件恢复的!这下终于找到症结了,使用SQL恢复时并将相应表的Index生成记录也恢复,以至于垃圾数据与现有的新生成的数据冲突了!
二话不说,先删除垃圾数据,本来想一条SQL搞定的,无奈4.0不支持增量查询,delete的where字句也不能使用in\not in作为谓词,晕,写程序吧,经过一个小时左右,各个功能搞定!OK
后来想起来,这个问题在平常使用时经常存在,总结一下吧:
- 级联删除要正确的配置
- 数据库的备份SQL慎用,尤其是在与数据库本身主键生成相关的项目。
- 数据库的数据一致性很重要