具有继承性的 JPA 映射树结构

JPA mapped tree structure with inheritance

我正在尝试在 JPA 中实现树结构,我想使用 EclipseLink 将其映射到 H2 数据库。树的节点可能是基节点 class 的子class。发生的事情是 EL 正在为 children 创建一个 brain-dead link table,如下所示:

[EL Fine]: sql: 2015-04-10 13:26:08.266--ServerSession(667346055)--Connection(873610597)--CREATE TABLE ORGANIZATIONNODE_ORGANIZATIONNODE (OrganizationNode_IDSTRING VARCHAR NOT NULL, children_IDSTRING VARCHAR NOT NULL, Device_IDSTRING VARCHAR NOT NULL, monitors_IDSTRING VARCHAR NOT NULL, PRIMARY KEY (OrganizationNode_IDSTRING, children_IDSTRING, Device_IDSTRING, monitors_IDSTRING))

OrganizationNode 是 Device 的正确上级class。这两个都是@Entity,OrganizationNode extends AbstractEntity,这是一个@MappedSuperclass,其中定义了@Id(它是一个字符串)。更奇怪的是,虽然有一个 Monitor class 不在树结构中,但 "monitors" 复数出现的唯一地方是作为 Device 的字段......什么?

现在,使用这样的 table 来实现树结构很好,但我不希望复合主键与每个子 class 的 Id 字段的单独实例!那必须打破 - 因为一些 children 不是设备,因此没有 "Device_IDSTRING",果然:

Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException|Internal Exception: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "DEVICE_IDSTRING"; SQL statement:|INSERT INTO ORGANIZATIONNODE_ORGANIZATIONNODE (children_IDSTRING, OrganizationNode_IDSTRING) VALUES (?, ?) [23502-186]|Error Code: 23502|Call: INSERT INTO ORGANIZATIONNODE_ORGANIZATIONNODE (children_IDSTRING, OrganizationNode_IDSTRING) VALUES (?, ?)|?bind => [2 parameters bound]|Query: DataModifyQuery(name="children" sql="INSERT INTO ORGANIZATIONNODE_ORGANIZATIONNODE (children_IDSTRING, OrganizationNode_IDSTRING) VALUES (?, ?)")

这看起来确实很奇怪。我已经尝试了我可能想到的所有映射注释组合来修复它。有什么想法吗?

类关注

AbstractEntity.java:

@MappedSuperclass
public abstract class AbstractEntity {
// @Converter(name="uuidConverter",converterClass=UUIDConverter.class)
transient UUID id = null;
@Id String idString;

static long sequence = 1;

static long GREGORIAN_EPOCH_OFFSET = 12219292800000L;

public AbstractEntity() {
    ThreadContext tctx = ThreadContext.getThreadContext();
    long msb = tctx.getNodeID();
    long lsb = (System.currentTimeMillis()+GREGORIAN_EPOCH_OFFSET) * 1000 + ((sequence++) % 1000);
    lsb = (lsb & 0xCFFFFFFFFFFFFFFFL) | (0x8000000000000000L);  
    msb = (msb & 0xFFFFFFFFFFFF0FFFL) | (0x0000000000001000L);
    id = new UUID(msb,lsb);
    idString = id.toString();
}

@Id
public UUID getUUID() {
    return id;
}

public String getIdString() {
    return idString;
}

public void setIdString(String idString) {
    this.idString = idString;
    this.id = UUID.fromString(idString);
}

void setUUID(UUID id) {
    this.id = id;
    this.idString = id.toString();
}

@Override
public String toString() {
    return "["+this.getClass().getCanonicalName()+" "+this.getUUID()+"]";
}
}

OrganizationNode.java:

@Entity
public class OrganizationNode extends AbstractEntity {

@ManyToOne(cascade=CascadeType.ALL)
NodeType nodeType;

@Column(nullable=true)
String name;

@OneToMany(cascade=CascadeType.ALL)
Set<OrganizationNode> children;

public OrganizationNode() {}

public OrganizationNode(NodeType nt, String name) {
    this.nodeType = nt;
    this.name = name;
    children = new HashSet<>();
}

public void setNodeType(NodeType nt) {
    nodeType = nt;
}

public NodeType getNodeType() {
   return nodeType;
}

public String getName() { 
    if ((name == null) || (name.equals(""))) return null;
    return name;
} 

public void setName(String name) { 
    this.name = name; 
}

public Set<OrganizationNode> getChildren() {
    return children;
}

public void setChildren(Set<OrganizationNode> children) {
    this.children = children;
} 

public void addNode(OrganizationNode node) {
    children.add(node);
}

public void removeNode(OrganizationNode node) {
    children.remove(node);
}
}

Device.java:

@Entity
public class Device extends OrganizationNode {

Set<Monitor> monitors;

public Device() {
   super();
}

public Device(NodeType nt, String name) {
    super(nt, name);
    monitors = new HashSet<>();
}

public Set<Monitor> getMonitors() {
    return monitors;
}

public void setMonitors(Set<Monitor> monitors) {
    this.monitors = monitors;
}

public void addMonitor(Monitor monitor) {
    monitors.add(monitor);
}

}

您需要决定要使用的继承策略。 默认值通常是 "Single Table Inheritance",因此所有子 class 都表示在一个 table 中,并带有合并列。

@Inheritance
@Entity
public class OrganizationNode extends AbstractEntity {
...
}

你看到了 sql。

您可以加​​入多重 Table 继承,其中每个子 class 都有自己的 table 并与父 table:

相连
@Inheritance(strategy=InheritanceType.JOINED)

最后,最后一个选项是Table Per Class Inheritance,其中没有"inheritance"树反映在tables结构中,每个对象都有它的完整 table 包含 class 和 supperclasses.

中的所有列
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)

最后一个效率最低。

  • 你可以有只有一个策略,你定义在继承(OrganizationNode)的顶部,它不能在子classes.

  • 默认的单一 table 继承通常是最有效的,除非确实有很多列在 classes

    [ 之间不共享。 =37=]
  • 您可能应该显式声明将用于破坏实际 class 类型的列:@DiscriminatorColumn(name="NODE_TYPE") 并为每个实体定义值:@DiscriminatorValue("TYPE1")