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

发布于 2024-10-04 21:49:07 字数 1988 浏览 1 评论 0原文

我有两个表:

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上有一个复合主键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 对象时,它会反射性地创建一个新实例并设置所有fields 为 null,因此在将其链接到之前保留的 Document 对象之前会丢失sectionNum 值。

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

I've got two tables:

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

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

Entries in the database might look like this:

Document:

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

Section:

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

Document has a generated Id on DOC_ID, while Section has a composite primary key over DOC_ID and SECTION_NUM.

SECTION_NUM is a locally(application) generated sequence number starting fresh for every document.

My entity classes look as follows:

@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;
}

When inserting a new Document and related Section, I do the following:

Document doc = new Document();

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

entityManager.persist(doc);

When persisting I get an exception stating that NULL is not allowed for column SECTION_NUM.
I'm using OpenEJB (which relies on OpenJPA behind the scenes for unit testing), and found when stepping through OpenJPA code that it successfully persists the Document object, but when it comes to the Section object it creates a new instance reflectively and sets all fields to null, so losing the sectionNum value, before linking it to the Document object persisted earlier.

Unfortunately I can't change the DB schema, as it's a legacy system.
Has anybody done something similar and got it working?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

对风讲故事 2024-10-11 21:49:07

一段时间以来我一直想更新这个,但太忙了...

好吧,事实证明这对于 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;
}

这只是澄清问题的简化版本。
真正的类也有一个Sections的集合:

@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 不会管理它。
持久化文档后,框架将调用 updateChildIds 函数,您可以在其中使用新持久化的文档 id 手动更新部分 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);
        }
    }
}

I've been meaning to update this for some time, but been too busy...

Ok, so it turns out this isn't really possible with JPA.
However, there is a workaround.

Previously I mentioned that the Document class looks like this.

@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;
}

That was just a shortened version to clarify the issue.
The real class has a collection of Sections too:

@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);
}

If Section had a simple primary key, JPA would easily handle the relationship, as it would accept an id from the application, or generate it from a sequence, but it won't do both with one id.

So, the solution is to manage the relationship yourself, and add a lifecycle function:

@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);
        }
    }
}

As you can see, the Section relationship is now Transient, meaning JPA won't manage it.
After persisting a Document, the framework will call the updateChildIds function, where you manually update the Section id's with the newly persisted Document id's.

This could be demonstrated in the following facade:

@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);
        }
    }
}
往昔成烟 2024-10-11 21:49:07

事实上,JPA 完全能够处理这个问题。您要查找的注释是MapsId

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

@MapsId("docId")

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

Actually, JPA is perfectly able to handle this. The annotation you are looking for is MapsId.

In your case, in your Section, on the docId you simply need to add the following:

@MapsId("docId")

The value of the MapsId annotation is the attribute name of your compound primary key (which in this case is the same)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文