Hibernate异常:键'PRIMARY'重复输入。

4

我在我的数据库中有这些(简化的)表格

enter image description here

我已经用这两个(简化的)类映射了这些表格

公司

@Entity
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    @JoinTable(name="company_employee", 
    joinColumns={@JoinColumn(name="company_id")},
    inverseJoinColumns={@JoinColumn(name="employee_id")})
    @OrderBy("name ASC")
    private SortedSet<Employee> employees = new TreeSet<Employee>();

    // contructors + getters + setters
 }

员工

@Entity
public class Employee implements Comparable<Employee> {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;

     private String name;

     @OneToOne(fetch = FetchType.LAZY, optional = true)
     @JoinTable(name="company_employee", 
        joinColumns={@JoinColumn(name="employee_id")},
        inverseJoinColumns={@JoinColumn(name="company_id")}
     )
     private Company company;

     @Override
     public int compareTo(Employee anotherEmployee) {
          return this.name.compareTo(anotherEmployee.name);
     }

     // contructors + getters + setters + equals + hashcode
 }

我正在使用SortedSet/TreeSetCompany中定义的员工中,以按名称排序的方式获取员工。
问题出现在我持久化对象时。我已经阅读过必须在两个方面建立关系的内容。我在持久化公司之前设置了公司中的员工和员工中的公司。当我尝试执行持久化时,我得到以下异常。
Exception in thread "main" javax.persistence.RollbackException: Error while committing the transaction
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:86)
    at es.rubioric.hibernate.MainTest.main(MainTest.java:43)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1692)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1602)
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:67)
    ... 1 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:207)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
    at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1314)
    at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:50)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:447)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:333)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:335)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1224)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:464)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2890)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2266)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:146)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:230)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
    ... 1 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '32-80' for key 'PRIMARY'
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
    at com.mysql.jdbc.Util.getInstance(Util.java:381)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3558)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3490)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1959)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2109)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2643)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2077)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2362)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2280)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2265)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
    ... 18 more

这是一个简化的代码,会生成该异常。[在@NicolasFilotto的评论后编辑了提交位置]

    public static void main(String[] args) throws ParseException {

         EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestDB");

         EntityManager em = emf.createEntityManager();
         EntityTransaction tx = em.getTransaction();
         tx.begin();

        try {
             Company c = new Company("Nike");

             Employee e1 = new Employee("Anthony");
             e1.setCompany(c);  // line 26
             Employee e2 = new Employee("Zenobia");
             e2.setCompany(c);   // line 28
             Employee e3 = new Employee("Chuck");
             e3.setCompany(c);   // line 30
             Employee e4 = new Employee("Bernard");
             e4.setCompany(c);  // line 32

             c.getEmployees().add(e1);
             c.getEmployees().add(e2);
             c.getEmployees().add(e3);
             c.getEmployees().add(e4);

             em.persist(c);

             tx.commit();

        } finally {
            em.close();
        }
    }

这些是由Hibernate内部执行的SQL语句。

Hibernate: insert into Company (name) values (?)
Hibernate: insert into Employee (name) values (?)
Hibernate: insert into company_employee (company_id, employee_id) values (?, ?)
Hibernate: insert into Employee (name) values (?)
Hibernate: insert into company_employee (company_id, employee_id) values (?, ?)
Hibernate: insert into Employee (name) values (?)
Hibernate: insert into company_employee (company_id, employee_id) values (?, ?)
Hibernate: insert into Employee (name) values (?)
Hibernate: insert into company_employee (company_id, employee_id) values (?, ?)
Hibernate: insert into company_employee (company_id, employee_id) values (?, ?)

如果我注释掉26、28、30和32行(标记如上),相同的代码就可以完美运行。但我想知道为什么会产生异常。为什么会有重复的关键字?
先感谢您的帮助。

我不认为在finally块中调用tx.commit()是一个好主意。 - Nicolas Filotto
@NicolasFilotto 遇到了同样的异常。为什么在 finally 块中放置 commit 不是一个好主意呢? - RubioRic
尝试先创建并持久化您的员工实例,然后创建公司实例,为每个员工实例调用setCompany方法,并将e1、e2、e3和e4添加到c.getEmployees()中。 - Nicolas Filotto
@NicolasFilotto 谢谢,但我可以通过注释第26行、28行等来正确地保存实体,但我不知道为什么会生成那个异常。我很好奇。你的方法意味着比我已经有的代码(更多的持久化语句)还要多,并且没有回答我的问题。 - RubioRic
我知道,但我怀疑JPA实现不能创建新实体并为每个新实体添加连接。它似乎适用于一个实体,但不适用于4个实体,请尝试注释第28、30和32行。 - Nicolas Filotto
显示剩余4条评论
1个回答

7

这些重复是由于公司-员工关系在两个实体中都被复制造成的。每个实体都设置为单向1:M(某些原因,其中一个方向使用了1:1),并且两个实体都使用相同的连接表,在JPA插入双方条目时会导致重复。

解决方案是将一侧标记为“mappedby”另一侧。

@OneToMany( mappedBy="company", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@OrderBy("name ASC")
private SortedSet<Employee> employees = new TreeSet<Employee>();

我曾认为Hibernate可以检测并避免在链接表中插入重复数据。我不知道我不能同时标记双向关联。感谢您的回答。 - RubioRic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接