基于外键的增量字段(JPA、Hibernate、Oracle DB)

Posted

技术标签:

【中文标题】基于外键的增量字段(JPA、Hibernate、Oracle DB)【英文标题】:Increment field based on foreign key (JPA, Hibernate, Oracle DB) 【发布时间】:2016-07-12 09:31:30 【问题描述】:

是否可以根据外键使字段自动递增?我想用 JPA (Hibernate) 和 Oracle DB 来实现这一点。我读过 mysqlInnoDB 引擎是不可能的。

这很像以下问题:

mysql-second-auto-increment-field-based-on-foreign-key mysql-auto-increment-based-on-foreign-key

但这些只是基于 MySQL 表本身。不是 JPA 和 Oracle DB。

hibernate-autoincrement-non-primary-key-based-on-foreign-key

正是我需要的,但遗憾的是没有答案。

作为一个具体的例子,考虑一个简单的例子。每个Student 有多个Books。 Book 有一个主键(定期递增),但它也有一个followUpNumber。这个数字从 1 开始,每添加一个 BookStudent 就会自动递增。这意味着每个Student 都有一个Book 列表,其中followUpNumber 每次都从1 开始。这是Book 表来显示我的意思:

|------|------------|-----------------|
|  id  | student_id | followup_number |
|------|------------|-----------------|
|  1   |      1     |        1        |
|  2   |      1     |        2        |
|  3   |      1     |        3        |
|  4   |      2     |        1        |
|  5   |      1     |        4        |
|  6   |      1     |        5        |
|  7   |      2     |        2        |
|  8   |      3     |        1        |
|  9   |      3     |        2        |
|------|------------|-----------------|
id 为主键 student_id 是学生的外键 followup_number 是每个 student_id 从 1 开始的字段

是否有类似于不需要放在主键字段上的自定义 @GeneratedValue 策略?我当前的解决方案是在添加新的Book 之前,遍历所有Books,获取最高的followup_number 并将该数字+1 分配给新书。但我觉得必须有一种更清洁、更优雅的方式来做到这一点。

【问题讨论】:

想到了插入时的数据库触发器,它不需要通过JPA来解决。但我很想看到面向 JPA 的解决方案。 【参考方案1】:

可以使用@OrderColumn 注解。

public @interface OrderColumn

指定用于 保持列表的持久顺序。持久性提供者是 负责在检索和在 数据库。持久性提供者负责更新 在刷新到数据库时排序以反映任何插入, 影响列表的删除或重新排序。

OrderColumn 注释在 OneToMany 或 ManyToMany 上指定 关系或元素集合。 OrderColumn 注释 在引用的关系的一侧指定 要订购的集合。订单列不可见 实体或可嵌入类的状态的一部分。

OrderBy 注释应该用于可见的排序 持久状态并由应用程序维护。订购者 指定 OrderColumn 时不使用注解。

订单列必须是整型。持久性提供者 保持一个连续的(非稀疏的)排序的值 更新关联或元素集合时的 ​​order 列。这 第一个元素的 order 列值为 0。

唯一的缺点是第一个元素的数字是0,而不是1


一个例子:

@Entity
@Table(name="STUDENT")
@SequenceGenerator(name="student_seq", initialValue=1, 
                   allocationSize=10,sequenceName="STUDDENT_SEQ")
public class Student 

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE,generator="student_seq")
    private Long Id;

    @Column(name="NAME")
    private String name;

    @OneToMany(cascade = CascadeType.ALL, mappedBy="student")
    @OrderColumn(name="FOLLOWUP_NUMBER")
    private List<Book> books = new ArrayList<>();

    ...............
    ...............


@Entity
@SequenceGenerator(name="book_seq", initialValue=1, 
                   allocationSize=10, sequenceName="BOOK_SEQ")
public class Book 

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE,generator="book_seq")
    private Long id;

    @ManyToOne
    @JoinColumn(name="STUDENT_ID")
    private Student student;

    @Column(name="TITLE", nullable=false)
    private String title;

    ..................
    ..................


还有这个简单的测试用例:

EntityTransaction tr = em.getTransaction();

tr.begin();

for (int j = 1; j < 10; j++) 
    Student student = new Student("Student name " + j);
    for (int i = 1; i <= 10; i++) 
        student.getBooks().add(new Book("Some book" + i, student));
    
    em.persist(student);


tr.commit();

生成以下表格(在 Oracle 12c 和 Hibernate 5.2 上测试):

select * from student;
   ID NAME               
----- --------------------
    1 Student name 1      
    2 Student name 2      
    3 Student name 3      
    4 Student name 4
    .......
    .......

select * from book
order by student_id, followup_number;
   ID TITLE                STUDENT_ID FOLLOWUP_NUMBER
----- -------------------- ---------- ---------------
    1 Some book1                    1          0
    2 Some book2                    1          1
    3 Some book3                    1          2
    4 Some book4                    1          3
    5 Some book5                    1          4
    6 Some book6                    1          5
    7 Some book7                    1          6
    8 Some book8                    1          7
    9 Some book9                    1          8
   10 Some book10                   1          9
   11 Some book1                    2          0
   12 Some book2                    2          1
   13 Some book3                    2          2
   14 Some book4                    2          3
   15 Some book5                    2          4
   16 Some book6                    2          5
   17 Some book7                    2          6
   18 Some book8                    2          7
   19 Some book9                    2          8
   20 Some book10                   2          9
   21 Some book1                    3          0
   22 Some book2                    3          1
   23 Some book3                    3          2
   ........
   ........

性能可能是另一个劣势,因为对于每一本书,Hibernate 生成两个 SQL 命令 - 一个插入,然后一个更新 FOLLOWUP_NUMBER 列。 更糟糕的是,当某本书被删除时,Hibernate 会生成所有再次为给定学生的数字并触发所有剩余书籍的FOLLOWUP_NUMBER 列的更新。


编辑


但困扰我的是“订单列作为 实体或可嵌入类的状态。”。数据和我一样 喜欢它在数据库中,但它没有反映到我的实体上。虽然我会 喜欢使用这个 FOLLOWUP_NUMBER

我不知道这在 JPA 中是否可行,但在这种情况下可以使用 Hibernate 扩展 @Formula

class Book
    .......
    .......

    @Formula(value="FOLLOWUP_NUMBER")
    private Long followUpNumber;

    public Long getFollowUpNumber() 
        return followUpNumber;
    

    ........
    ........

这个计算字段甚至可以被查询,例如下面的测试用例:

Query query = em.createQuery(
   "Select b FROM Book b JOIN b.student s " +
   "WHERE s.name = :studentname AND b.followUpNumber = :follownbr"
);
query.setParameter("studentname", "Student name 6");
query.setParameter("follownbr", Long.valueOf(4));
Book book = (Book)query.getSingleResult();

System.out.println("Found book = " + book.getTitle());
System.out.println("book follow nbr= " + book.getFollowUpNumber());

给出以下结果:

Found book = Some book5
book follow nbr= 4

Hibernate 使用以下 SQL 查找该书:

    select
        book0_.id as id1_0_,
        book0_.STUDENT_ID as STUDENT_ID3_0_,
        book0_.TITLE as TITLE2_0_,
        book0_.FOLLOWUP_NUMBER as formula0_ 
    from
        Book book0_ 
    inner join
        STUDENT student1_ 
            on book0_.STUDENT_ID=student1_.Id 
    where
        student1_.NAME=? 
        and book0_.FOLLOWUP_NUMBER=?

【讨论】:

很高兴看到 JPA 有一个可能的解决方案。但困扰我的是“订单列作为实体或可嵌入类的状态的一部分是不可见的。”。数据就像我在数据库中想要的那样,但它没有反映到我的实体上。虽然我想利用这个FOLLOWUP_NUMBER. 在这种情况下可以使用 @Formula 扩展名,我在答案中附加了一个示例。

以上是关于基于外键的增量字段(JPA、Hibernate、Oracle DB)的主要内容,如果未能解决你的问题,请参考以下文章

java之hibernate之基于外键的双向一对一关联映射

hibernate笔记--基于主键的单(双)向的一对一映射关系

hibernate基础08:关联映射之基于外键的双向一对一

hibernate 之 关联映射的基于外键的双向一对一关联

hibernate 之 关联映射的基于外键的单向一对一映射

Hibernate JPA与共享主键外键字段的一对一双向映射为空(hb无法自动设置)