JPA (Java Persistence API)

Table of Contents

1. JPA 简介

The Java Persistence API (JPA) provides Java developers with an object/relational mapping facility for managing relational data in Java applications.

Java Persistence consists of four areas:

  • The Java Persistence API (defined in package javax.persistence)
  • The query language
  • The Java Persistence Criteria API
  • Object/relational mapping metadata

为什么要用 JPA?
使用 JPA 可简化 Java 的数据库编程,使开发者从繁琐的 JDBC 和 SQL 代码中解脱出来。

说明:JPA 并不是 Java EE 环境专用,JPA 是通用 API,不依赖于 Java EE 容器,所以在 Java SE 环境中也可以使用。

参考:
The Java EE 6 Tutorial, Part VI Persistence: http://docs.oracle.com/javaee/6/tutorial/doc/bnbpy.html
TopLink JPA Annotation Reference: http://www.oracle.com/technetwork/middleware/ias/toplink-jpa-annotations-096251.html#Transient
http://www.tutorialspoint.com/jpa/index.htm

1.1. JPA 实现

下面是 JPA 的一些具体实现:

  • Hibernate: The most advanced and widely used. Pay attention for the classpath because a lot of libraries are used, especially when using JBoss. Supports JPA 2.1.
  • EclipseLink: Is based on TopLink, and is the intended path forward for persistence for Oracle and TopLink. Supports JPA 2.1.
  • Apache OpenJPA: Best documentation but seems very buggy. Open source implementation for JPA. Supports JPA 2.0.
  • DataNucleus: Well documented, open source (Apache 2 license), is also a JDO provider. Supports JPA 2.1.
  • ObjectDB: Well documented, fast Object Database for Java with JPA 2 and JDO 2 support.
  • MyBatis: A fork of iBatis, open source (Apache 2 license).

1.2. JDO (Java Data Objects) vs. JPA

There are two standard API's for persistence - Java Data Objects (JDO) and Java Persistence API (JPA). The former (JDO) is designed for all datastores, and the latter is designed for RDBMS datastores only.

参考:
Persistence API : JDO or JPA ?
Which Persistence Specification ?

2. Overview to JPA

2.1. Class Level Architecture

The following image shows the class level architecture of JPA. It shows the core classes and interfaces of JPA.

jpa_class_level_architecture.png

Figure 1: JPA Class Level Architecture

The following table describes each of the units shown in the above architecture.

Table 1: JPA core classes and description
Units Description
EntityManagerFactory This is a factory class of EntityManager. It creates and manages multiple EntityManager instances.
EntityManager It is an Interface, it manages the persistence operations on objects. It works like factory for Query instance.
Entity Entities are the persistence objects, stores as records in the database.
EntityTransaction It has one-to-one relationship with EntityManager. For each EntityManager, operations are maintained by EntityTransaction class.
Persistence This class contain static methods to obtain EntityManagerFactory instance.
Query This interface is implemented by each JPA vendor to obtain relational objects that meet the criteria.

The EntityManager represents the application session or dialog with the database. Each request, or each client will use its own EntityManager to access the database. In JPA, a database connection is represented by the EntityManager interface.

参考:
http://www.objectdb.com/java/jpa/start/connection
http://www.tutorialspoint.com/jpa/jpa_architecture.htm

2.2. Object-relational Mapping (ORM)

Object-relational mapping (ORM, O/RM, and O/R mapping) is a programming technique for converting data between relational databases and object oriented programming languages such as Java, C# etc.

参考:http://www.tutorialspoint.com/hibernate/orm_overview.htm

2.3. Java Persistence Query Language (JPQL)

JPQL is Java Persistence Query Language defined in JPA specification. It is used to create queries against entities to store in a relational database. JPQL is developed based on SQL syntax. But it won't affect the database directly.

参考:
https://www.tutorialspoint.com/jpa/jpa_jpql.htm
https://en.wikibooks.org/wiki/Java_Persistence/JPQL
https://en.wikipedia.org/wiki/Java_Persistence_Query_Language

3. Object-Relational Mapping (ORM)

3.1. 基本映射

Entity 类对应着数据库表。

数据库对象 Annotion 其它 Annotion
Table @Entity @Table(name="Table1")
column - @Column(name="Col1", nullable=false, length = 512, ...)
primary key @Id @GeneratedValue(strategy = GenerationType.AUTO)
NONE (不映射到数据库列中) @Transient  

除了使用 Annotion 指定映射外,也可以把映射关系写在 orm.xml 文件中。
例如,下面的 Annotion:

package com.mytest;

import javax.persistence.*;
...
@Entity
@Table(name="EMPLOYEE1")
public class Employee {
    @Id
    private long id;
    private String firstName;
    private String lastName;
    private Address address;
    ...
}

相当于 orm.xml 文件中下面配置:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0"
        xmlns="http://java.sun.com/xml/ns/persistence/orm"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd">
    <description>The minimal mappings for a persistent entity in XML.</description>
    <entity name="Employee" class="com.mytest.Employee" access="FIELD">
        <table name="EMPLOYEE1"/>
        <attributes>
            <id name="id"/>
        </attributes>
    </entity>
</entity-mappings>

3.1.1. 列注解可写在 field 前或 getter 前

列注解可写在 field 前或相应 getter 前,两种方法都是合法的。
例如,下面两种方式是等价的:

 列注解写在 field 前               列注解写在 getter 前           
 @Entity                         
 @Table(name = "contact")        
 public class ContactInfo {      
   @Column(name = "contact_nm")  
   private String name;          
                                 
   public String getName() {     
     return name;                
   }                             
   ....                          
 }                               
 @Entity                        
 @Table(name = "contact")       
 public class ContactInfo {     
   private String name;         
                                
   @Column(name = "contact_nm") 
   public String getName() {    
     return name;               
   }                            
   ....                         
 }                              

3.1.2. Table 的高级映射

一般地,一个 Java 类对应一个数据库表。下面这些高级的用法也支持:

  • Multiple tables(@SecondaryTable) : One class maps to 2 or multiple tables.
  • Sharing tables : 2 or multiple classes are stored in the same table.
  • Inheritance : A class is involved in inheritance and has an inherited and local table.
  • Views : A class maps to a view.
  • Stored procedures : A class maps to a set of stored procedures.
  • Partitioning : Some instances of a class map to one table, and other instances to another table.
  • Replication : A class's data is replicated to multiple tables.
  • History : A class has historical data.

参考:https://en.wikibooks.org/wiki/Java_Persistence/Tables#Advanced

3.2. 主键(@Id)

数据库中主键(Primary Key)对应 JPA 中的@Id 注释。

参考:https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing

3.2.1. Id 自动生成策略

一般来说,插入数据时应用程序需要自己提供主键的值。不过,JPA 也提供了几种 Id 自动生成策略:
(1) GenerationType.AUTO ,由 JPA 自动生成(在生产环境下不要使用这个配置,它底层是使用其他 3 种生成策略,如果底层使用 SEQUENCE 或者 TABLE,很可能由于权限问题而无法创建 SEQUENCE 或者 TABLE)
(2) GenerationType.IDENTITY,使用数据库的自增长字段,需要数据库的支持(如 SQL Server、MySQL、DB2、Derby 等)
(3) GenerationType.SEQUENCE,使用数据库的序列号,需要数据库的支持(如 Oracle 等等)
(4) GenerationType.TABLE,使用指定的数据库表记录 ID 的增长,需要定义一个 TableGenerator,在@GeneratedValue 中引用。

下面是使用 GenerationType.SEQUENCE 的简单例子。

@Entity
public class Employee implements Serializable {
    ...
    @Id
    @GeneratedValue(strategy=SEQUENCE, generator="CUST_SEQ")
    @Column(name="CUST_ID")
    public Long getId() {
        return id;
    }
    ...
}

3.2.2. Composite Primary Keys (@IdClass, @EmbeddedId)

有时,数据库主键不是单独的一列,而是由多列组成,这就是 Composite Primary Keys。

JPA 中有两种方式可以实现 Composite Primary Keys:方式一:@IdClass;方式二:@EmbeddedId。

下面是使用方式一实现 Composite Primary Keys 的例子:

@Entity
@IdClass(ProjectPK.class)
public class Project {    //表Project的主键由departmentId和projectId组成
    @Id
    int departmentId;     // 注:每个key还是要指定@Id

    @Id
    long projectId;
    ......
}

Class ProjectPK {
    int departmentId;
    long projectId;
    // 需要实现 equals 方法
}

下面是使用方式二实现 Composite Primary Keys 的例子:

@Entity
public class Project {    //表Project的主键由departmentId和projectId组成
    @EmbeddedId ProjectPK id;
    ......
}

@Embeddable
Class ProjectPK {
    int departmentId;
    long projectId;
    // 需要实现 equals 方法
}

3.3. 外键

JPA 定义了 one-to-one、one-to-many、many-to-one、many-to-many 四种关系。
参考:https://en.wikibooks.org/wiki/Java_Persistence

3.3.1. OneToOne

假设有下面两个表:

Table 2: EMPLOYEE (table)
EMP_ID FIRSTNAME LASTNAME SALARY ADDRESS_ID
1 Bob Way 50000F 6
2 Sarah Smith 60000 7
Table 3: ADDRESS (table)
ADDRESS_ID STREET CITY PROVINCE COUNTRY P_CODE
6 17 Bank St Ottawa ON Canada K2H7Z5
7 22 Main St Toronto ON Canada L5H2D5

相应 Entity 可如下定义:

@Entity
public class Employee {
  @Id
  @Column(name="EMP_ID")
  private long id;
  ...
  @OneToOne(fetch=FetchType.LAZY)
  @JoinColumn(name="ADDRESS_ID")
  private Address address;

}
@Entity
public class Address {
  @Id
  @Column(name = "ADDRESS_ID")
  private long id;
  ...
  @OneToOne(fetch=FetchType.LAZY, mappedBy="address")
  private Employee owner;
  ...
}

3.3.2. OneToMany/ManyToOne

假设有下面两个表:

Table 4: EMPLOYEE (table)
EMP_ID FIRSTNAME LASTNAME SALARY MANAGER_ID
1 Bob Way 50000 2
2 Sarah Smith 75000 null
Table 5: PHONE (table)
ID TYPE AREA_CODE P_NUMBER OWNER_ID
1 home 613 792-0000 1
2 work 613 896-1234 1
3 work 416 123-4444 2

相应 Entity 可如下定义:

@Entity
public class Employee {
  @Id
  @Column(name="EMP_ID")
  private long id;
  ...
  @OneToMany(mappedBy="owner")
  private List<Phone> phones;
  ...
}
@Entity
public class Phone {
  @Id
  private long id;
  ...
  @ManyToOne(fetch=FetchType.LAZY)
  @JoinColumn(name="OWNER_ID")
  private Employee owner;
  ...
}

3.3.3. ManyToMany

4. Appendix

4.1. JPA 使用实例

下面演示一个使用 JPA 的实例(运行环境为 EclipseLink 和 MySQL)。
参考:http://www.tutorialspoint.com/jpa/jpa_entity_managers.htm

第一步,创建 Entity 类(对应于数据库的表)。如:

package com.tutorialspoint.eclipselink.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table
public class Employee {

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private int eid;

   private String ename;
   private double salary;
   private String deg;

   public Employee(int eid, String ename, double salary, String deg) {
      super( );
      this.eid = eid;
      this.ename = ename;
      this.salary = salary;
      this.deg = deg;
   }

   public Employee( ) {
      super();
   }

   public int getEid( ) {
      return eid;
   }

   public void setEid(int eid) {
      this.eid = eid;
   }

   public String getEname( ) {
      return ename;
   }

   public void setEname(String ename) {
      this.ename = ename;
   }

   public double getSalary( ) {
      return salary;
   }

   public void setSalary(double salary) {
      this.salary = salary;
   }

   public String getDeg( ) {
      return deg;
   }

   public void setDeg(String deg) {
      this.deg = deg;
   }

   @Override
   public String toString() {
      return "Employee [eid=" + eid + ", ename=" + ename + ", salary=" + salary + ", deg=" + deg + "]";
   }
}

第二步,准备好 Persistence.xml 文件。

<?xml version="1.0" encoding="UTF-8"?>

<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
   http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

   <persistence-unit name="Eclipselink_JPA" transaction-type="RESOURCE_LOCAL">

      <class>com.tutorialspoint.eclipselink.entity.Employee</class>

      <properties>
         <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
         <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpadb"/>
         <property name="javax.persistence.jdbc.user" value="root"/>
         <property name="javax.persistence.jdbc.password" value="root"/>
         <property name="eclipselink.logging.level" value="FINE"/>
         <property name="eclipselink.ddl-generation" value="create-tables"/>
      </properties>

   </persistence-unit>
</persistence>

第三步,编写代码,实现 CRUD 操作。

如持久化数据(增加记录)到数据库:

package com.tutorialspoint.eclipselink.service;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import com.tutorialspoint.eclipselink.entity.Employee;

public class CreateEmployee {

   public static void main( String[ ] args ) {

      EntityManagerFactory emfactory = Persistence.createEntityManagerFactory( "Eclipselink_JPA" );

      EntityManager entitymanager = emfactory.createEntityManager( );
      entitymanager.getTransaction( ).begin( );

      Employee employee = new Employee( );
      employee.setEid( 1201 );
      employee.setEname( "Gopal" );
      employee.setSalary( 40000 );
      employee.setDeg( "Technical Manager" );

      entitymanager.persist( employee );
      entitymanager.getTransaction( ).commit( );

      entitymanager.close( );
      emfactory.close( );
   }
}

如查找记录:

package com.tutorialspoint.eclipselink.service;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import com.tutorialspoint.eclipselink.entity.Employee;

public class FindEmployee {
   public static void main( String[ ] args ) {

      EntityManagerFactory emfactory = Persistence.createEntityManagerFactory( "Eclipselink_JPA" );
      EntityManager entitymanager = emfactory.createEntityManager();
      Employee employee = entitymanager.find( Employee.class, 1201 );

      System.out.println("employee ID = " + employee.getEid( ));
      System.out.println("employee NAME = " + employee.getEname( ));
      System.out.println("employee SALARY = " + employee.getSalary( ));
      System.out.println("employee DESIGNATION = " + employee.getDeg( ));
   }
}

上面程序,正常运行时会输出:

employee ID = 1201
employee NAME = Gopal
employee SALARY = 46000.0
employee DESIGNATION = Technical Manager

4.2. Data Access Object (DAO) Pattern

In computer software, a data access object (DAO) is an object that provides an abstract interface to some type of database or other persistence mechanism. By mapping application calls to the persistence layer, DAO provide some specific data operations without exposing details of the database.

A simple example can help you understand the concept.
Let's say we have an entity to represent an employee:

public class Employee {
    private int id;
    private String name;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

The employee entities will be persisted into a corresponding Employee table in a database. A simple DAO interface to handle the database operation required to manipulate an employee entity will be like:

interface EmployeeDAO {
    List<Employee> findAll();
    List<Employee> findById();
    List<Employee> findByName();
    boolean insertEmployee(Employee employee);
    boolean updateEmployee(Employee employee);
    boolean deleteEmployee(Employee employee);
}

Next we have to provide a concrete implementation for that interface to deal with SQL server, and another to deal with flat files, etc...

参考:
Core J2EE Patterns - Data Access Object: http://www.oracle.com/technetwork/java/dataaccessobject-138824.html
Data access object (DAO) in java: http://stackoverflow.com/questions/19154202/data-access-object-dao-in-java
Data Access Object Pattern: http://www.informit.com/guides/content.aspx?g=java&seqNum=137

Author: cig01

Created: <2015-11-21 Sat>

Last updated: <2017-12-13 Wed>

Creator: Emacs 27.1 (Org mode 9.4)