基于外键的增量字段(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 来实现这一点。我读过 mysql
的 InnoDB
引擎是不可能的。
这很像以下问题:
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
有多个Book
s。 Book
有一个主键(定期递增),但它也有一个followUpNumber
。这个数字从 1 开始,每添加一个 Book
到 Student
就会自动递增。这意味着每个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
之前,遍历所有Book
s,获取最高的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)的主要内容,如果未能解决你的问题,请参考以下文章