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 00:00>

Last updated: <2017-12-13 Wed 13:39>

Creator: Emacs 25.3.1 (Org mode 9.1.4)