【问题标题】:JPA, Mixed surrogate key with foreign key and sequence numberJPA,具有外键和序列号的混合代理键
【发布时间】:2010-11-30 00:27:20
【问题描述】:

我有两张桌子:

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 对象时,它反射性地创建了一个新实例并设置了所有字段为空,因此在将其链接到 Document 对象之前丢失了 sectionNum 值。

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

【问题讨论】:

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


    【解决方案1】:

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

    好的,事实证明这对于 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 不会管理它。 持久化 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);
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

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

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

      @MapsId("docId")
      

      MapsId 注解的值是你的复合主键的属性名(在这种情况下是相同的)

      【讨论】:

      猜你喜欢
      • 2020-02-19
      • 1970-01-01
      • 1970-01-01
      • 2018-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-02
      • 2016-06-05
      相关资源
      最近更新 更多