JPA,具有外键和序列号的混合代理键

时间:2010-11-30 00:27:20

标签: jpa foreign-keys sequence composite-key surrogate-key

我有两张桌子:

DOCUMENT
--------
DOC_ID (PK)
.
.
.

SECTION
-------
DOC_ID (FK, PK)
SECTION_NUM (PK)
.
.
.

数据库中的条目可能如下所示:

文件:

DOC_ID | . . .
--------------
1      | . . .
2      | . . .

部分:

DOC_ID | SECTION_NUM | . . .
---------------------------
1      | 1           | . . .
1      | 2           | . . .
1      | 3           | . . .
2      | 1           | . . .

Document在DOC_ID上生成了ID,而Section在DOC_ID和SECTION_NUM上有一个复合主键。

SECTION_NUM是一个本地(应用程序)生成的序列号,每个文档都是新的。

我的实体类如下所示:

@Entity
@Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
    @Id
    @Column(name = "DOC_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DocIdSeq")
    @SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
    private Long docId;
}


@Entity
@Table(name = "SECTION")
@IdClass(SectionId.class)
public class Section implements java.io.Serializable {
    @Id
    @Column(name = "DOC_ID", nullable = false)
    private Long docId;

    @Id
    @Column(name = "SECTION_NUM", nullable = false)
    private Integer sectionNum;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "DOC_ID")
    private Document document;
}

public class SectionId implements java.io.Serializable {
    private Long docId;
    private Integer sectionNum;
}

插入新文档及相关章节时,我会执行以下操作:

Document doc = new Document();

Section section = new Section();
section.setDocument(doc);
section.setSectionNum(1);

entityManager.persist(doc);

持久化时,我得到一个异常,声明列SECTION_NUM不允许NULL。 我正在使用OpenEJB(它在后台依赖OpenJPA进行单元测试),并且在步进OpenJPA代码时发现它成功地持久化了Document对象,但是当涉及到Section对象时,它反射地创建了一个新实例并设置了所有字段为null,因此在将之前链接到Document对象之前丢失了sectionNum值。

不幸的是,我无法更改数据库架构,因为它是一个遗留系统。 有没有人做过类似的事情并让它发挥作用?

2 个答案:

答案 0 :(得分:0)

我一直想更新这个,但是太忙了......

好的,事实证明这对JPA来说是不可能的。 但是,有一种解决方法。

以前我提到Document类看起来像这样。

@Entity
@Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
    @Id
    @Column(name = "DOC_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
    "DocIdSeq")
    @SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
    private Long docId;
}

这只是一个缩短版本以澄清问题。 真正的班级也有一系列章节:

@Entity
@Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
    @Id
    @Column(name = "DOC_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"DocIdSeq")
    @SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
    private Long docId;

    @OneToMany
    private Set<Section> sections = new HashSet<Section>(0);
}

如果Section有一个简单的主键,JPA可以很容易地处理这个关系,因为它接受来自应用程序的id,或者从序列中生成它,但它不能同时使用一个id。

因此,解决方案是自己管理关系,并添加生命周期功能:

@Entity
@Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
    @Id
    @Column(name = "DOC_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
    "DocIdSeq")
    @SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
    private Long docId;

    @Transient
    private Set<Section> sections = new HashSet<Section>(0);

    @PostPersist
    public void updateChildIds() {
        for (Section section : this.sections) {
            section.getId().setDocId(this.docId);
        }
    }
}

如您所见,Section关系现在是Transient,这意味着JPA不会管理它。 在持久化Document之后,框架将调用updateChildIds函数,您可以使用新持久化的Document id手动更新Section id。

这可以通过以下方面证明:

@Stateless
public void DocumentFacade implements DocumentFacadeLocal {

    @PersistenceContext
    private EntityManager entityManager;

    public void save(Document entity) throws Exception {
        this.entityManager.persist(entity);
        this.entityManager.flush();
        this.persistTransientEntities(entity);
        this.entityManager.flush();
    }

    private void persistTransientEntities(CaseInstructionSheet entity) {
       for (Section section : entity.getSections()) {
            this.entityManager.persist(section);
        }
    }
}

答案 1 :(得分:0)

实际上,JPA完全能够解决这个问题。您要查找的注释是MapsId

在您的情况下,在Section,在docId上,您只需添加以下内容:

@MapsId("docId")

MapsId注释的值是复合主键的属性名称(在本例中是相同的)