我们遇到的问题是,当使用eclipselink持久化实体时,持久化引用具有继承层次结构的其他表的表会遇到约束冲突。
请参阅以下实体:
Base
类:
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@lombok.Data
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
@Entity
@Table(name="base")
public class Base implements Serializable {
@Id
@NotNull
@Size(min = 1, max = 50)
@Column(name = "name")
private String name;
@Size(max = 500)
@Column(name = "description")
private String description;
public Base() {
}
public Base(final String name) {
this.name= name;
}
public Base(final String name, final String description) {
this.name= name;
this.description= description;
}
}
Target
类继承自Base
:
import java.io.*;
import javax.persistence.*;
@lombok.Data
@lombok.ToString(callSuper = true)
@Entity
@Table(name="target")
public class Target extends Base implements Serializable {
public Target() {
}
public Target(final String name) {
super(name);
}
public Target(final String name, final String description) {
super(name, description);
}
}
Source
类引用了Target
:
import java.io.*;
import javax.persistence.*;
@lombok.Data
@lombok.ToString
@Entity
@Table(name = "source")
public class Source implements Serializable {
@Id
@SequenceGenerator(name = "source_id_seq_gen", sequenceName = "source_id_seq", allocationSize=1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "source_id_seq_gen")
private long id;
/** The assigned ProjectAttribute. */
@ManyToOne
@JoinColumn(name="target")
private Target target;
protected Source() {
}
public Source(final Target target) {
this.target= target;
}
}
以及相应的数据库结构:
CREATE TABLE base (
dtype VARCHAR(16),
name VARCHAR(50),
description VARCHAR(500),
PRIMARY KEY (name)
);
CREATE TABLE target (
name VARCHAR(50),
PRIMARY KEY (name) ,
FOREIGN KEY (name) REFERENCES base (name)
);
CREATE SEQUENCE baz_id_seq START WITH 1 INCREMENT BY 1;
CREATE TABLE baz (
id BIGINT,
name VARCHAR(50),
PRIMARY KEY (id)
);
CREATE SEQUENCE source_id_seq START WITH 1 INCREMENT BY 1;
CREATE TABLE source (
id BIGINT,
baz BIGINT,
target VARCHAR(50),
PRIMARY KEY (id),
FOREIGN KEY (baz) REFERENCES baz (id) ON DELETE CASCADE,
FOREIGN KEY (target) REFERENCES target (name) ON DELETE CASCADE
);
persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="myPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!--provider>org.hibernate.jpa.HibernatePersistenceProvider</provider-->
<class>eu.herrn.eclipselink.entities.Target</class>
<class>eu.herrn.eclipselink.entities.Source</class>
<class>eu.herrn.eclipselink.entities.Baz</class>
<class>eu.herrn.eclipselink.entities.Base</class>
<properties>
<property name="eclipselink.logging.parameters" value="true"/>
<property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/mydb"/>
<property name="javax.persistence.jdbc.user" value="mydb"/>
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
<property name="javax.persistence.jdbc.password" value="mydb"/>
</properties>
</persistence-unit>
</persistence>
和一个简单的测试类:
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class EclipseLinkTest {
private static final EntityManagerFactory FACTORY= Persistence.createEntityManagerFactory("myPU");
public EclipseLinkTest() {
}
public static void main(String[] args) {
final EntityManager em= FACTORY.createEntityManager();
// cleanup db
em.getTransaction().begin();
em.createQuery("DELETE FROM Source s").executeUpdate();
em.createQuery("DELETE FROM Target t").executeUpdate();
em.createQuery("DELETE FROM Base b").executeUpdate();
em.getTransaction().commit();
em.getTransaction().begin();
final Target pa1= new Target("target", "dummy Target");
em.persist(pa1);
// em.flush(); //this shouldn't be necessary
final Source apa1= new Source(pa1);
em.persist(apa1);
em.getTransaction().commit();
System.out.println("--- finished");
FACTORY.close();
}
}
上面的代码因违反约束而失败:
Internal Exception: org.apache.derby.shared.common.error.DerbySQLIntegrityConstraintViolationException: INSERT on table 'SOURCE' caused a violation of foreign key constraint 'SQL181012111707551' for key (target). The statement has been rolled back.
Error Code: 0
Call: INSERT INTO source (ID, target) VALUES (?, ?)
bind => [12, target]
Query: InsertObjectQuery(Source(id=12, target=Target(super=Base(name=target, description=dummy Target))))
Exception in thread "main" javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.3.v20180807-4be1041): org.eclipse.persistence.exceptions.DatabaseException
激活上面示例中注释的em.flush()
时,代码运行正常。
现在,违反约束的原因是什么?看来EclipseLink只是没有按正确的顺序持久化实体。 Base
并因此Target
具有未生成的ID,因此问题不在于持久时尚不知道的ID。但是即使那样,我仍然认为eclipselink会自行处理,因为一切都在同一事务中运行。
使用Hibernate作为持久性管理器时,不会发生此问题。摆脱Base
类时也不会发生这种情况,因此似乎与这些表的继承有关。
这是EclipseLink中的错误吗?