Pro JPA2 第二章(入门)
JPA的主要目标之一是简单易用和易于理解.虽然它的问题域不容忽视或者淡化,但是解决问题的技术非常简单和直观.
本章首先将描述实体(entity)的基本特征.定义什么是实体,以及如何创建,读取,更新和删除实体.还将介绍实体管理器(EntityManager)以及如何获取和使用它们.接着,将快速了解查询(Query)以及如何使用EntityManager和Query对象指定和执行查询.然后最后会有一个总结性的小例子.
2.1 实体概述
实体的描述为:有特性和关系的事物,期望把它的特性和关系保存在关系数据中.实体基本上是一个名词,或者是一组状态关联在一起而形成的单个单元.在面向对象的范例中,将行为添加到实体中,并称之为对象.在JPA中,任何应用程序定义的对象都可以是一个实体.因此,一个重要问题也许是:是的对象变成实体的特征是什么?- 2.1.1 持久性
实体的第一个和最基本的特征是它们是可持久化的.这意味着可以在一个数据存储中表示它们的装啊提,并且将来可以对他们进行访问.
这样的实体我们称之为持久化对象(persistence object).但从技术角度来说这是不正确的.严格来讲,一个持久化对象只有在内存中实例化的时刻才变得持久.如果存在一个持久化对象,那么根据定义它已经是持久的.
为了使实体具有持久性,应用程序必须主动地调用API方法来启动该过程.这是一个重要的区别,因为它把持久化的控制权完全留给了应用程序.
要点是实体可能不一定必须被持久化,他们是否应该被持久化将由应用程序来决定. - 2.1.2 标识
类似任何其他的Java对象,实体具有一个对象标识(identity),但是当它存在于数据库中时,还用友一个持久化标识.持久化标识,或一个标识符(identifier),是唯一标识一个实体实例并区别与其他所有相同的实体类型实例的关键.
也就是说,在数据库表中存在一行,那么它会具有一个持久化标识,如果不在数据库中,那么,即使在内存中的实体可以在一个字段中设置标识,它也不具有持久化标识. - 2.1.3 事务性
我们可以称实体为半事务性的(quasi-transactional).
在内存中,其跟数据库的事务稍微有点不同.因为实体可能会发生改变,但这些改变并没有被持久化. - 2.1.4 粒度
实体不是基本类型(primitive),基本类型封装器(primitive wrapper) 或者具有单维状态(single-dimensional state)的内置对象.这些类型都仅仅是标量(scalar),对应用程序并没有任何固有的语义含义.
实体意味着是细粒度的对象,它包含一个聚合状态集合,他们通常存储在单个位置.最通常的意义下,它们是业务域对象,对于访问它们的应用程序具有特定含义. - 2.2 实体元数据
除了其持久化的状态,每个实体都包含一些相关的元数据来描述它.这些元数据可能作为保存的类文件的一部分存在,或者可能在类的外部存储,但不是保存在数据库中.元数据是的持久化层从加载到在运行时调用实体,均能够识别,结束以及正确地管理它们.
每个实体实际所需的元数据都是最小量的,以确保实体已于定义和使用.
两种方式指定实体的元数据:注解(Annotation)或者XML.
- 2.2.1 注解
元数据注解允许把结构化和类型化的元数据附加到源代码上.因为注解使得元数据与进程同时存在,所以不必转义到额外的文件和特别的语言(XML)中. - 2.2.2 XML
依然可以使用XML来使用元数据 - 2.2.3 异常配置
异常配置(configuration by exception)的概念意味着持久化引擎定义了适用于大多数应用程序的默认值,用于只有在希望覆盖默认值时,才需要提供值.换句话说,提供配置值是规则的异常情况,而不是必需的.
- 2.2.1 注解
- 2.1.1 持久性
2.3 创建实体
@Entity public class Employee { @Id private int id; private String name; private long salary; public Employee() {} public Employee(int id) { this.id = id; } }
说明:
- @Entity注解 这仅仅是一个标记注解,用于通知持久化引擎 该类是一个实体.
- @Id注解 它注解了特定字段或属性用于保存实体的持久化标识(主键).它是必需的.
Id注解可以写在字段或者属性上.
当其写在字段上时,叫字段访问
当其写在getter方法上时,叫属性访问.此时,将忽略实体字段,仅使用getter和setter方法进行命名,也就是说,此时,数据库的字段名称是根据getter方法来产生的.跟字段没有直接关系.
2.4 实体管理器
前面已经指出,实体在数据库中真正获得持久化之前,需要调用一个特定的API.这个API就由实体管理器实现,它几乎完全封装在成为EntityManager的单个接口中.持久化的真正工作需要委托给实体管理器来完成.(注意理解前面持久化的概念,想想为什么要把实体持久化的操作交给单独的API,也就是实体管理器.)
通过显式地把实体作为参数传递到一个方法调用,或者是从数据库直接读取它,实体管理器能够获得一个实体的引用.此时,将该对象称之为由实体管理器管理.实体管理器在任何给定的时间内所管理的实体实例的结合,称为持久化上下文(persistence context).
在任何时候,具有相同持久化标识的Java实例,在一个持久化上下文中只能存在一个.2.4.1 获取实体管理器
实体管理器总是从一个EntityManagerFactory获取.获取实体管理器的这个工厂将确定控制其操作的配置参数.在Java SE环境中,有一个Psersistence的简单引导类.
获得EntityManagerFactory:EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");
通过EntityManagerFactory创建EntityManager:
EntityManager em = emf.createEntityManager();
2.4.2 持久化实体
持久化一个实体值得是:获得一个临时实体,或者在数据库中尚未有任何持久化表示的实体,然后存储该实体的状态是的能够在后续对其进行检索.Employee emp = new Employee(158); em.persist(emp);
当persist()调用返回时,emp将成为在实体管理器的持久化上下文中的一个托管实体.
2.4.3 寻找实体
Employee emp = em.find(Employee.class,158);
返回的Employee实体将是一个托管实体,它将存在于当前实体管理器所关联的持久化上下文中.
如果对象已被删除或者如果我们提供了错误的id,则程序将会返回null.这个地方我们需要做null检查.2.4.4 删除实体
Employee emp = em.find(Employee.class,158); em.remove(emp);
如果在调用remove()之前,没有进行null检查,则有可能会抛出java.lang.IllegalArgumentException.
要被删除的实体,必须是一个托管实体.2.4.5 更新实体
Employee emp = em.find(Employee.class,158); emp.setSalary(emp.getSalary()+100; em.merge(emp);
要更新的实体,必须是一个托管实体.
- 2.4.6 事务
详细请参考第六章. - 2.4.7 查询
JPA的查询类似于数据库查询,但它不适用结构化查询语言(SQL)来指定查询条件,而是对实体进行查询,并且其使用Java持久化查询语言(Java Persistence Query Language,JPQL)的查询语言.
查询在代码中实现为一个Query活TypedQuery对象.它们使用EntityManager作为工厂来构建.
可以动态或静态地定义查询.静态查询可以在注解或者XML元数据中定义,它必须包括查询条件以及用户指定的名称.这类查询也称为命名查询(named query),在执行时,通过名称来查找它.
动态查询可以在运行时通过提供JPQL查询条件或一个条件对象来生成.当然这会引入一些额外的执行开销.
2.5 汇总
操作Employee实体的服务类:package pro.jpa.service; import pro.jpa.entity.Employee; import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.*; import java.util.ArrayList; import java.util.List; public class EmployeeService { protected EntityManager em; public EmployeeService(EntityManager em) { this.em = em; } public Employee create(int id, String name, long salary) { Employee emp = new Employee(id); emp.setName(name); emp.setSalary(salary); em.persist(emp); return emp; } public void remove(int id) { Employee emp = this.find(id); if (null != emp) { em.remove(emp); } } public Employee find(int id) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Employee.class); Root<Employee> root = cq.from(Employee.class); List<Selection> selections = new ArrayList<>(); selections.add(root.get("id")); cq.multiselect(selections); //cq.select(root); TypedQuery<Employee> query = em.createQuery(cq); List<Predicate> criteria = new ArrayList<>(); ParameterExpression<Integer> p = cb.parameter(Integer.class, "id"); criteria.add(cb.equal(root.get("id"), p)); return query.getSingleResult(); } public Employee updateSalary(int id, long raise) { Employee emp = em.find(Employee.class, id); if (null != emp) { emp.setSalary(emp.getSalary() + raise); } return emp; } public List<Employee> findAll() { Query query = em.createNativeQuery("SELECT e.* FROM Employee e"); return query.getResultList(); } }
测试EmployeeService:
package pro.jpa.service; import org.junit.Before; import org.junit.Test; import pro.jpa.entity.Employee; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import java.util.List; public class EmployeeTest { EntityManagerFactory emf; EntityManager em; EmployeeService es; @Before public void before() { emf = Persistence.createEntityManagerFactory("EmployeeService"); em = emf.createEntityManager(); es = new EmployeeService(em); } public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService"); EntityManager em = emf.createEntityManager(); EmployeeService es = new EmployeeService(em); // create and persist an employee em.getTransaction().begin(); Employee emp = es.create(1166, "aaa", 40000000); em.getTransaction().commit(); System.out.println("Persisted " + emp); // find a specific employee emp = es.find(158); System.out.println("Found " + emp); // find all employees List<Employee> emps = es.findAll(); emps.forEach(e -> { System.out.println("Found employee " + e); }); // update the employee em.getTransaction().begin(); emp = es.updateSalary(158, 2000); em.getTransaction().commit(); System.out.println("Updated " + emp); // remove an employee em.getTransaction().begin(); es.remove(158); em.getTransaction().commit(); System.out.println("Removed Employee 158"); em.close(); emf.close(); } @Test public void testFind() { Employee emp = es.find(158); System.out.println(emp.getId()); System.out.println(emp.getName()); System.out.println(emp.getSalary()); } @Test public void testSave() { es.create(999, "aaa", 40l); } }
2.6 组装
JPA的基本构建模块已经了解.现在我们要把它们组合在一起运行在Java SE的环境上.2.6.1 持久化单元
描述持久化单元(persistence unit)的配置定义在一个成为persistence.xml的XML文件中.
单个persistenc.xml文件可以包含一个或多个已命名的持久化单元配置,但是每个持久化单元是独立于和有别于其他持久性单元的,它们在逻辑上可以认为是存在于单独的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="EmployeeService" transaction-type="RESOURCE_LOCAL"> <class>pro.jpa.entity.Employee</class> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/projpa"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.password" value="root"/> </properties> </persistence-unit> </persistence>
- 2.6.2 持久化存档文件
把持久性的项目组装在一起,我们宽松地称之为持久化存档文件(Persistence Archive).它实际上就是JAR格式的文件,在META-INF目录下包含persistence.xml文件,并且一般包含实体类文件.