Java Persistence API (JPA)
最权威的资料是JPA 3.0规范文件,2020年9月8日,写得很清楚,质量很高,啃下来不算太难。或者看JPA 3.0规范PDF版本。
实体 Entity
An entity is a lightweight persistent domain object.
Entity类:
- 标记为
@Entity或在XML中标记为Entity - 有一个无参的constructor,public或protected
- 不能是nested class,不能是enum或interface
- 不能是final,方法和要存储的变量都不能是final
- 如果一个entity实例要传输为detached对象,就必须实现Serializable interface
- 支持inheritance、polymorphic associations和polymorphic queries
- 可以是抽象类,可以继承非entity类,也可以继承entity类;非entity类也可以继承entity类
- entity的persistence state由instance变量表示(可以是JavaBeans属性)。对客户来说,必须由access methods(getter和setter)访问
- entity类可以有business方法
@Entity
public class Customer implements Serializable {
private Long id;
private String name;
private Address address;
private Collection<Order> orders = new HashSet();
private Set<PhoneNumber> phones = new HashSet();
// No-arg constructor
public Customer() {}
@Id // property access is used
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@OneToMany
public Collection<Order> getOrders() {
return orders;
}
public void setOrders(Collection<Order> orders) {
this.orders = orders;
}
@ManyToMany
public Set<PhoneNumber> getPhones() {
return phones;
}
public void setPhones(Set<PhoneNumber> phones) {
this.phones = phones;
}
// Business method to add a phone number to the customer
public void addPhone(PhoneNumber phone) {
this.getPhones().add(phone);
// Update the phone entity instance to refer to this customer
phone.addCustomer(this);
}
}
这里是可以有一些商业逻辑的。
注:这里的讨论全都忽略了@Embeddable和复合主键,太麻烦,需要的话再看文档。继承也不在此详述。
实体属性 Attributes
Field或者property,不标注的话默认是@Basic。
JPA provider访问类型
这里的访问类型指的是JPA provider是直接访问field还是property。除非标为@Transient,所有field或者property都会被persist。选择任一访问类型皆可,视具体需求而定。
主键、ID
简单主键是最容易的,直接加@Id即可。复合主键很麻烦,尽量不要用。
集合 Collections
一个attribute可以是一个集合,这个集合要么是普通元素,如果不是entity的话,就得mark为@ElementCollection,数据存在某个表里。如果是entity的话,则用@OneToMany或@ManyToMany。
Map集合
无论是集合元素普通元素还是entity,我们都可以用java.util.Map而不仅仅是Collection来表示。这样我们就可以快速地根据主键或者其它属性获取需要的元素。
以entity为例,本来写作Collection<Book> books的现在可以写为Map<String, Book> books,其中key可以是Book的@Id,也可以是别的attribute。
如果map的key是基础类型,可以用@MapKeyColumn标注哪一列是该key。如果key是个entity,则用@MapKeyJoinColumn或@MapKeyJoinColumns。如果不是用generics的话,要用@MapKeyClass标注数据类型。
如果map的key是map的value的主键或某个attribute,则可以直接用@MapKey标注。
如果value是基础类型,要用一个@CollectionTable。
如果value是entity,则对于@ManyToMany使用一个join表,对unidirectional的@OneToMany默认也使用一个join表。如果@ManyToOne或@OneToMany是bidirectional的,则默认不需要join表。
实体关系 Entity Relationships
实体间关系是polymorphic的。TODO:什么意思?
实体间关系类型:
- @OneToOne
- @OneToMany
- @ManyToOne
- @ManyToMany
关系可以是bidirectional或unidirectional的。双向关系是一方own的,另一方并不own这个关系。另一方虽然也标注关系类型,但是只用mappedBy标注对应的own这个关系的entity的attribute。拥有的一方总是多的那一方,比如@ManyToOne,而不能是@OneToMany。单向关系,另一方不用标注。
| Owning Side | Inverse Side (w/ mappedBy),没有的话是单向关系 | 备注 |
|---|---|---|
| @OneToOne | @OneToOne | |
| @ManyToOne | @OneToMany | |
| @ManyToMany | @ManyToMany | 要有join表 |
| @OneToOne | ||
| @OneToMany | 要有join表 | |
| @ManyToOne | ||
| @ManyToMany | 要有join表 |
只有保存拥有关系的一方时,改变才会被cascade到另一方。见3.2.4. Synchronization to the Database。
It is particularly important to ensure that changes to the inverse side of a relationship result in appropriate updates on the owning side, so as to ensure the changes are not lost when they are synchronized to the database.
例子:@OneToOne、@ManyToOne(@OneToMany w/ mappedBy)
@Entity
public class Employee { // 默认表是EMPLOYEE
private Cubicle assignedCubicle; // 默认外键叫ASSIGNEDCUBICLE_<PK_CUBICLE>,且是unique的
@OneToOne
public Cubicle getAssignedCubicle() {
return assignedCubicle;
}
public void setAssignedCubicle(Cubicle cubicle) {
this.assignedCubicle = cubicle;
}
// ...
}
@Entity
public class Cubicle { // 默认表是CUBICLE
private Employee residentEmployee;
@OneToOne(mappedBy="assignedCubicle")
public Employee getResidentEmployee() {
return residentEmployee;
}
public void setResidentEmployee(Employee employee) {
this.residentEmployee = employee;
}
// ...
}
例子:@ManyToMany
@Entity
public class Project { // 默认表是PROJECT
private Collection<Employee> employees; // 默认外键是EMPLOYEES_<PK_EMPLOYEE>
@ManyToMany
public Collection<Employee> getEmployees() { // 默认join表是PROJECT_EMPLOYEE
return employees;
}
public void setEmployees(Collection<Employee> employees) {
this.employees = employees;
}
// ...
}
@Entity
public class Employee { // 默认表是EMPLOYEE
private Collection<Project> projects; // 默认外键是PROJECTS_<PK_PROJECT>
@ManyToMany(mappedBy="employees")
public Collection<Project> getProjects() {
return projects;
}
public void setProjects(Collection<Project> projects) {
this.projects = projects;
}
// ...
}
实体实例生命周期 Entity Instance's Life Cycle
任何一个entity实例都属于以下四种状态之一。
- 新建的 new
- 被管理的 managed
- 脱离的 detached
- 已移除的 removed
| 生命周期 | Persistence Identity | Persistence Context | 备注 |
|---|---|---|---|
| New | 无 | 无 | 新建的 |
| Manged | 有 | 有 | |
| Detached | 有 | 无 | |
| Removed | 有 | 有 | Transaction提交时将移除 |
stateDiagram-v2
[*] --> new : new Book()
new --> managed: persist()
managed --> managed: persist()
removed --> managed: persist()
new --> new: remove()
managed --> removed: remove()
注意,persist()和remove()会根据cascade类型处理引用的entity。

实体管理器 EntityManager
一个EntityManager对应一个persistence context的。
使用示例:
@Stateless
public class OrderEntryBean implements OrderEntry {
@PersistenceContext
EntityManager em;
public void enterOrder(int custID, Order newOrder) {
Customer cust = em.find(Customer.class, custID);
cust.getOrders().add(newOrder);
newOrder.setCustomer(cust);
em.persist(newOrder);
}
}
持久化境 Persistence Context
A persistence context is a set of managed entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed by the entity manager.
实体打包 Entity Packaging
持久化单位 Persistence Unit
持久化单位一个在在逻辑上grouping:
- 实体管理器工厂和实体管理器,及它们的配置信息
- Managed类
- 映射元数据(Java标注或XML)
persistence.xml
一个持久化单位是定义在一个persistence.xml中的,一个persistence.xml可以有多个持久化单位。
persistence.xml放在META-INF目录中。
API
JPA的API都在javax.persistence这个package中,当然还需要配合实际的JPA provider使用。
EntityManager
注意,在保存的时候,@OneToMany的关系是不会被保存的,因为这个关系是属于有@ManyToOne的那个Entity的,必须要单独保存那个Entity。参见Why merging is not cascaded on a one to many relationship - Stack Overflow。似乎可以标记cascade type,不过手动的话就是这样。
- find
- persist
- merge
- contains
- detach
- flush
- remove
- refresh
什么是JPA?
JPA is a part of Jakarta EE (formerly Java EE, EE stands for Enterprise Edition). It provides ORM (object-relational mapping).
What ways of query does JPA have?
- JPQL (Java Persistence Querying Language): similar to SQL.
- Criteria API: no embedded language, similar to Django's model layer.
- Native SQL
It supports many other ways of query.
什么是JPA provider?
Just like JDBC, Java only provides a set of interface, the actual work is done by providers. In JDBC, the API is in java.sql, and the actual providers are JDBC drivers provided by database vendors or third-parties. Some notable JPA providers: EclipseLink, Hibernate and OpenJPA, etc.
JPA支持哪些关系?
The basic ones:
- OneToOne: A - B
- ManyToOne: A *- B
- OneToMany: A -* B
- ManyToMany: A *-* B, realised by A - A_B - B where A_B is a JoinTable
怎么映射ManyToMany关系?
For example, if you have table A, B and A_B where A_B is a JoinTable of A and B. You can define A in JPA as:
@Entity
public class A {
@ManyToMany
@JoinTable(name = "A_B",
joinColumns = @JoinColumn(name = "A_ID", referencedColumnName = "ID"),
inverseJoinColumns = @JoinColumn(name = "B_ID", referencedColumnName = "ID"))
private List<B> bs;
}
B as:
@Entity
public class B {
@ManyToMany(mappedBy="bs")
private List<A> as;
}
If the JoinTable has more columns other than A and B's ids, you can fallback and use ManyToOne on the JoinTable.