原生JPA使用教程、JPA查询部分属性、JPA关联查询、JPA的QBC方式查询(CriteriaQuery方式查询)、JPA执行存储过程

文章目录

一、介绍(认识了解JPA)

此文章基于 JPA 4.2.4 和 mysql 5 版本来介绍,和Spring整合的时候用的是Spring4.0版本。不同的版本会有一些差异。所以此文章仅供参考

此文章基于 JPA 4.2.4 和 mysql 5 版本来介绍,和Spring整合的时候用的是Spring4.0版本。不同的版本会有一些差异。所以此文章仅供参考

先了解一下jdbc再说那个jpa就更加容易理解了。

在这里插入图片描述

在早期的软件开发历史中,数据库技术的发展催生了多种数据库产品,例如MySQL、Oracle、DB2和SQL Server等。这些数据库系统各自拥有独特的特性和优势,但同时也带来了一个问题:开发者在使用Java语言开发应用程序时,需要针对不同的数据库系统学习并使用各自的API。 例如,MySQL提供了一套API,Oracle、DB2和SQL Server也分别提供了自己的API。这种多样性虽然在一定程度上满足了不同场景的需求,但也给开发者带来了额外的学习成本和开发负担。每当需要切换或集成不同的数据库产品时,开发者都需要重新学习和适应新的API,这无疑增加了开发效率和维护成本。

为了解决这一问题,SUM公司这个组织就提出了一种新的解决方案。SUM公司制定了一系列规范,这套规范被称为Java数据库连接(JDBC)规范。JDBC规范的核心是定义了一组标准的接口,这些接口为Java应用程序访问数据库提供了统一的编程模型。 SUM公司并没有提供这些接口的具体实现,而是将这一任务交给了各个数据库厂商。数据库厂商根据JDBC规范实现了这些接口,并将实现打包成JDBC驱动程序(通常以jar包的形式提供)。这样,无论开发者使用哪种数据库,都可以通过统一的JDBC接口进行操作,极大地简化了数据库访问的复杂性。

通过这种方式,JDBC规范实现了Java应用程序与各种数据库之间的解耦,使得开发者无需关心底层数据库的具体实现,只需关注如何使用JDBC接口来完成数据库操作。这不仅提高了开发效率,也使得应用程序更加灵活,便于维护和扩展。JDBC规范的推出,是软件开发领域中一次重要的标准化工作,它促进了Java技术在企业级应用开发中的广泛应用。

在很早以前,出现了很多的数据库如mysql、oracle、db2、sqlserver等等,开发者在开发 Java 应用程序如果想要访问和使用这些个数据库,需要这些数据库厂商提供一套 API 才能让 java 应用程序进行访问和使用。不同的数据库厂商都按照自己的方式提供API,mysql提供了一套、oracle提供了一套、db2提供了一套、sqlserver提供了一套,开发者在开发java应用程序时就可以通过数据库厂商提供的 API 去访问使用数据库了,这样虽然能用,但开发者觉得使用不同的数据库要用不同的API还是挺麻烦的,比如,换个数据库又要去学新的数据库的API,还有其他的麻烦事项这里就不列举了。这个时候sum公司也任务这样做不好,于是sum公司就出一套规范,这套规范叫JDBC,这里面实际上是定义了一组接口,JDBC里边并没有提供这组接口的实现类,sum公司说就由各个数据库厂商提供这组接口的实现类,这些实现类就变成了一个jar包,就是所谓的jdbc驱动。所以说jdbc本身是一套规范是一组接口,他统一了java应用程序访问数据库的标准。

JDBC 出现后,随着软件开发领域的发展,数据库操作的需求日益增长,这促使了多种对象关系映射(ORM)框架的诞生。比如,我们熟悉的Hibernate,还有不那么广为人知的TopLink等,还有其他的ORM框架。这些框架旨在简化数据库的交互和管理,使得开发者能够更加高效地处理数据持久化任务。然而,每个ORM框架都有其独特的API和使用方式,这导致了开发者在学习和使用不同框架时需要投入额外的时间和精力。

为了解决这一问题,SUM公司提出了一个新的标准(规范)—— Java Persistence API (翻译过来就是:Java持久化API;简称:JPA)。JPA的目标是为了方便开发者使用统一的方式来访问和操作不同的ORM框架。通过JPA规范,开发者无需针对每个ORM框架学习特定的API,而是只需遵循JPA的编程模型,就能够实现对数据库的持久化操作。

在这里插入图片描述

JPA规范定义了一系列的接口和类,这些接口和类为数据模型的映射、事务管理、查询机制等方面提供了标准的方法。这样,无论开发者选择使用Hibernate、TopLink还是其他任何遵循JPA规范的ORM框架,他们都可以通过相同的JPA API来进行数据库操作

JPA的出现极大地简化了Java开发者与数据库交互的工作。它不仅降低了学习成本,提高了开发效率,还增强了应用程序的可维护性和可移植性。通过JPA,开发者可以更加专注于业务逻辑的实现,而不是繁琐的数据库操作细节,从而推动了Java企业级应用开发的进一步发展。

阅读完上面的文字可以有下面的认识理解:

  • JPA就是Java Persistence API (翻译过来就是:Java持久化API;简称:JPA)。用于对象持久化的 API
  • JPA是一个规范,是一个 ORM 框架(对象-关系映射框架)规范,(而且他是Java EE 5.0 平台标准的 ORM 规范)
  • JPA简化了Java开发者与数据库交互的工作,降低了学习成本,降低了维护和移植的难度,提高了开发效率
  • JPA的目标是为了方便开发者使用统一的方式来访问和操作不同的ORM框架;JPA出现也是希望整合ORM技术,实现天下归一

JPA与hibernate的关系

  • JPA 是 hibernate 的一个抽象(就像JDBC和JDBC驱动的关系)

    • 上面说了JPA的出现是用于统一java应用程序访问各个ORM框架的方式。所以JPA就是一个规范;他本质上是一种 ORM 规范,不是ORM 框架。因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现。
    • Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现
  • 从功能上来说, JPA Hibernate 功能的一个子集

简单一点说就是,JPA是规范,hibernate是实现。一个是规范一个实现,那么 hibernate 的功能就比那个规范更强大,所以说JPA是hibernate功能的一个子集

既然这里说到了hibernate,那就多说点。从另外一个角度来说,以前我们学hibernate的时候,主要是使用配置文件的方式,而JPA呢,我们全程使用注解,也可以这样认为:我们现在是去学一个使用注解的方式去用这个hibernate;当然API不一样。而这些注解呢是javaEE5.0标准里边带的,所以说他们并不需要引入第三方的jar包,就除了这个JPA跟Hibernate之外,它不需要第三方的。这是标准的东西,看到了吧,所以说从另外一个角度的话我们实际上也是学一学如何使用注解的方式来搞这个hibernate


JPA规范的一些实现

JPA 的目标之一是制定一个可以由很多供应商实现的 API,目前Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的实现。著名的实现就是hibernate

  • Hibernate:实际上JPA 作者和 Hibernate 的作者有密切关系,hibernate首当其冲;Hibernate 从 3.2 开始兼容 JPA
  • OpenJPA:OpenJPA 是 Apache 组织提供的开源项目

  • TopLink:TopLink 以前需要收费,如今开源了

OpenJPA和TopLink就不详细说了。


JPA的优势

PA(Java Persistence API)作为一种广泛使用的ORM(对象关系映射)框架,具有许多优点和优势,这些特性使其成为Java EE平台上数据持久化的首选解决方案。以下是JPA的一些主要优点和优势:

  1. 简化数据访问:JPA通过提供一套标准化的API,使得开发者能够以面向对象的方式处理数据,而无需编写大量的SQL代码。这大大简化了数据访问层的实现,提高了开发效率。
  2. 与数据库无关性:JPA的实现(如Hibernate、EclipseLink等)提供了数据库无关性,这意味着开发者可以编写与特定数据库无关的代码。这种抽象层次使得应用程序更容易在不同的数据库之间迁移,而无需对代码进行大量修改。
  3. 支持丰富的持久化操作:JPA提供了丰富的持久化操作,包括实体的创建、更新、删除和查询。这些操作通过实体管理器(EntityManager)进行管理,使得数据操作更加统一和一致。
  4. 支持多种查询方式:JPA支持JPQL(Java Persistence Query Language)和Criteria API两种查询方式。JPQL是一种类似于SQL的查询语言,它允许开发者以面向对象的方式编写查询。Criteria API则提供了一种类型安全的查询构建方式,使得查询更加灵活和可维护。
  5. 事务管理:JPA提供了声明式和编程式两种事务管理方式。声明式事务通过注解或XML配置来管理事务的边界,而编程式事务则允许开发者在代码中显式地控制事务。这为事务管理提供了灵活性,同时也简化了事务的配置和使用。
  6. 缓存支持:JPA的实现通常包含一级和二级缓存。一级缓存是实体管理器的缓存,它减少了对数据库的访问次数,提高了性能。二级缓存可以跨多个实体管理器实例共享,进一步提高了应用程序的性能。
  7. 支持懒加载和级联操作:JPA支持懒加载,这意味着实体的某些属性或关联实体可以在真正需要时才从数据库中加载。同时,JPA还支持级联操作,如级联保存、级联更新和级联删除,这使得实体之间的关系管理更加方便。
  8. 易于维护和扩展:由于JPA提供了一套标准化的API和配置方式,因此维护和扩展应用程序变得更加容易。开发者可以通过添加或修改实体类和配置来适应业务需求的变化,而无需重写大量的数据访问代码。
  9. 社区支持和文档:JPA作为一个成熟的规范,拥有大量的社区支持和丰富的文档资源。这为开发者提供了学习和解决问题的便利,同时也有助于提高开发效率。
  10. 与Spring框架的集成:JPA可以与Spring框架无缝集成,利用Spring的依赖注入和声明式事务管理,可以进一步简化数据访问层的配置和使用。

综上所述,JPA通过提供标准化的数据持久化API、丰富的功能和良好的扩展性,为Java开发者在进行数据访问和持久化操作时提供了极大的便利。


JPA 包括 3方面的技术

ORM 映射元数据:JPA 支持 XML JDK 5.0 注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。

JPA API:用来操作实体对象,执行CRUD操作,框架在后台完成所有的事情,开发者从繁琐的 JDBC和 SQL代码中解脱出来。

查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序和具体的 SQL 紧密耦合。

二、入门案例

1、创建工程

可以用eclipse创建JPA工程,new–>找到 JPA Project创建就行;也可以创建普通的java工程,也可用maven创建普通的java工程

2、加入依赖

maven的方式

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.xxx.jpademo</groupId>
  <artifactId>jpademo01</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>jpademo01</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
  	<!-- hibernate 依赖 -->
  	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
		<version>4.2.4.Final</version>
	</dependency>
	
	<!-- 要用jpa就要加这个依赖 -->
	<dependency>
		<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-entitymanager</artifactId>
	    <version>4.2.4.Final</version>
	</dependency>
	
		<!-- 数据库驱动 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.6</version>
		<scope>runtime</scope>
	</dependency>
  
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  
  <build>
  	<plugins>  
      <plugin>  
      <!-- 配适合自己的版本 -->
        <groupId>org.apache.maven.plugins</groupId>  
        <artifactId>maven-compiler-plugin</artifactId>  
        <configuration>  
          <source>1.8</source>  
          <target>1.8</target>  
        </configuration>  
      </plugin>  
    </plugins> 
  </build>
  
</project>

在这里插入图片描述

3、编写persistence.xml文件

persistence.xml这个实际上是JPA最基本的配置文件,这里面会包含连接数据库的基本信息,会包含使用哪个ORM框架来作为JPA的基本实现,也会配置使用事务的方式。

注意:persistence.xml,文件的名称是固定的,放的文件夹也是固定的,他需要放在类路径下的META-INF 目录下。

<?xml version="1.0" encoding="UTF-8"?>
<!-- 这里介绍是用 version="2.0"的 eclipse创建jpa工程的时候可以选这个版本 -->
<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">
	<!-- 
		事务方式(transaction-type):
		    transaction-type  默认是JTA,JTA是可以使用分布式事务。 我们这里直接用本地事务就可以了 (transaction-type=RESOURCE_LOCAL)
		    
		    name  表示持久化单元的名字。   创建EntitymanagerFactory需要用到
	-->
	
	<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
		<!-- 
			配置使用什么 ORM 产品来作为 JPA 的实现 
			1. 实际上配置的是  javax.persistence.spi.PersistenceProvider 接口的实现类
			2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点. 
		-->
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		
		<!-- 添加持久化类 -->
		<class>com.xxx.jpademo.jpademo01.pojo.Customer</class>
	
		
		<properties>
			<!-- 连接数据库的基本信息 -->
			<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
			<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa"/>
			<property name="javax.persistence.jdbc.user" value="root"/>
			<property name="javax.persistence.jdbc.password" value="root"/>
			
			<!-- 配置 JPA 实现产品的基本属性。   因为用hibernate作为JPA的实现,这里说白了就是配置 hibernate 的基本属性 -->
			<property name="hibernate.format_sql" value="true"/>
			<property name="hibernate.show_sql" value="true"/>
			<property name="hibernate.hbm2ddl.auto" value="update"/>
			<property name="hibernate.dialect" value="org.hibernate.dialect.xxxx"/> <!-- 在这个包下找到合适自己的数据库方言。不配置有些数据库可能会报错。 -->
			
		</properties>
	</persistence-unit>
</persistence>

4、创建实体类

使用 annotation 来描述实体类跟数据库表之间的映射关系。

package com.xxx.jpademo.jpademo01.pojo;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

@Table(name="jpa_cutomers")// 表示这个类关联的数据表是 jpa_cutomers
@Entity                    // 表示这个类是一个持久化类。
public class Customer {
	
	private Integer id;
	private String lastName;
	private String email;
	private int age;
	
	private Date birth;
	private Date createTime;
	
	
	public Customer() {}
	
	public Customer(Integer id, String lastName, String email, int age,Date birth,Date createTime) {
		super();
		this.id = id;
		this.lastName = lastName;
		this.email = email;
		this.age = age;
		this.birth = birth;//年月日时分秒的时间格式,而且都是datetime类型
		this.createTime = createTime;
	}
	
	@GeneratedValue(strategy=GenerationType.AUTO)// 表示生成主键的方式; 这些注解可以在getter方法上加。。
	@Id  // 标注了这个 @Id 注解,表示这个java属性对应数据库表中的主键字段
	public Integer getId() {
        /**
         * 
         * @GeneratedValue(strategy="xxx") 表示生成主键的方式,通过 strategy 属性指定生成策略
         *     strategy = "AUTO" 表示自动的,他将根据底层数据库的情况选用对应的主键生成方式;
         *     strategy = "IDENTITY" 表示显式指定数据库自增
         */
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
    
	@Column(name="last_name",length=50,nullable=false)
	public String getLastName() {
        /**
         * @Column注解 当实体的属性(java字段)与其映射的数据库表的列不同名时需要使用;可与 @Id 标注一起使用
         * 不加这个注解,就默认用java字段对应数据库的字段。java里面是 lastName 数据库表中的字段名就是lastName
         */
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getEmail() {
        // 不加 @Column 注解,就默认用java字段对应数据库的字段。java里面是 email 数据库表中的字段名就是email
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Temporal(TemporalType.DATE)
	public Date getBirth() {
		return birth;
	}
	public void setBirth(Date birth) {
		this.birth = birth;
	}
	
	@Column(name="create_time")
	@Temporal(TemporalType.TIMESTAMP)
	public Date getCreateTime() {
         /**
         * @Column注解 当实体的属性(java字段)与其映射的数据库表的列不同名时需要使用;
         * 不加这个注解,就默认用java字段对应数据库的字段。java里面是 lastName 数据库表中的字段名就是lastName
         * 
         *     如果是java是Date类型 ,数据库默认就会是datetime类型   年月日时分秒的时间格式。
         *     
         *     在java字段上加上@Temporal(TemporalType.DATE)      就是  年月日格式  数据类型就是date
         *     在java字段上加上@Temporal(TemporalType.TIME)      就是  时分秒格式  数据类型就是time
         *     在java字段上加上@Temporal(TemporalType.TIMESTAMP) 就是  年月日时分秒格式  数据类型就是timestamp
         */
		return createTime;
	}

	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}

	
	//一个特殊的方法,不需要映射为数据库表的一列。加上@Transient
	@Transient
	public String getInfo() {
		return "不想让info作为数据库的一列就加上@Transient。因为不加会默认加上@Basic";
	}
	
	@Override
	public String toString() {
		return "Customer [id=" + id + ", lastName=" + lastName + ", email=" + email + ", age=" + age + "]";
	}
	// 写完这个类后要记得在 persistence.xml 文件中的<persistence-unit>节点下面 添加<class>节点。就是写上这个类的全类名。具体写法查看上面的 persistence.xml
    // 写完这个类后要记得在 persistence.xml 文件中的<persistence-unit>节点下面 添加<class>节点。就是写上这个类的全类名。具体写法查看上面的 persistence.xml
    // 写完这个类后要记得在 persistence.xml 文件中的<persistence-unit>节点下面 添加<class>节点。就是写上这个类的全类名。具体写法查看上面的 persistence.xml
    // 写完这个类后要记得在 persistence.xml 文件中的<persistence-unit>节点下面 添加<class>节点。就是写上这个类的全类名。具体写法查看上面的 persistence.xml
}

5、编写main方法测试

package com.xxx.jpademo.jpademo01;

import java.util.Date;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import com.xxx.jpademo.jpademo01.pojo.Customer;


public class App 
{
    public static void main( String[] args )
    {
    	//1. 创建 EntitymanagerFactory
        String persistenceUnitName = "jpa-1"; //这个就是persistence.xml里面的持久化单元的名字   就是<persistence-unit>节点的name属性

        EntityManagerFactory entityManagerFactory = 
            Persistence.createEntityManagerFactory(persistenceUnitName);

        //2. 创建 EntityManager. 类似于 Hibernate 的 Session
        EntityManager entityManager = entityManagerFactory.createEntityManager();

        //3. 开启事务
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();

        //4. 进行持久化操作
        Customer customer = new Customer();
        customer.setAge(12);
        customer.setEmail("tom@atguigu.com");
        customer.setLastName("Tom");
        customer.setBirth(new Date());
        customer.setCreateTime(new Date());

        entityManager.persist(customer);

        //5. 提交事务
        transaction.commit();

        //6. 关闭 EntityManager
        entityManager.close();

        //7. 关闭 EntityManagerFactory
        entityManagerFactory.close();
    }
}

三、JPA常用注解和函数方法的解释

1、常用的注解

都是 javax.persistence.*这个包下的。如果以后发现不是再说

1.1、@Entity

标注用于实体类声明语句之前,指出该Java 类为实体类,将映射到指定的数据库表(就是说在一个类上标注了这个注解就表示这个类跟某一个数据表有对应关系了,如果不加上@Table一起使用就会默认将类名作为表名)。

例如:

import  javax.persistence.Entity;
// 声明一个实体类 Customer,在类名前写上@Entity,它将映射到数据库中的 Customer 表上。
@Entity
public class Customer {
    .....
}

注意这个注解如果不加上@Table一起使用就会默认将类名作为表名

1.2、@Table

当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,这个注解与 @Entity 标注并列使用,置于实体类声明语句之前,可写于单独语句行,也可与声明语句同行。

例如:

import  javax.persistence.Table;
import  javax.persistence.Entity;
@Table(name="jpa_cutomers") // 表示这个实体类映射的是数据库中的 jpa_cutomers 表
@Entity
public class Customer {
    .....
}

@Table注解它的一些属性

  • name:用于指明数据库的表名

  • 还有两个选项 catalog 和 schema 用于设置表所属的数据库目录或模式,通常为数据库名。(比如 oracle 可以用 schema = “xx” 指定哪个用户)

  • uniqueConstraints :用于设置约束条件,通常不须设置

1.3、@Id

用于声明一个实体类的属性映射为数据库的主键列。(注解可以写在类中的字段属性上面,也可以写在getter方法上面)

例如:

import  javax.persistence.*;

@Table(name="jpa_cutomers") // 表示这个实体类映射的是数据库中的 jpa_cutomers 表
@Entity
public class Customer {
    
    private Integer id;
    ....
        
	// 注解可以写在类中的字段属性上面,也可以写在getter方法上面
    @Column(name="数据库主键列")  // 当主键列和字段一样时 这个注解可以不写
    @GeneratedValue(strategy=GenerationType.AUTO)// 表示主键值生成的方式策略是什么
    @Id
    public Integer getId() {
        return id;
    }
    
    ....
}

1.4、@GeneratedValue

用于标注主键的生成策略,通过 strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:SqlServer 对应 identity,MySQL 对应 auto increment

import  javax.persistence.*;

@Table(name="jpa_cutomers") // 表示这个实体类映射的是数据库中的 jpa_cutomers 表
@Entity
public class Customer {
    
    private Integer id;
    ....
        
	// 注解可以写在类中的字段属性上面,也可以写在getter方法上面
    @Column(name="数据库主键列")  // 当主键列和字段一样时 这个注解可以不写
    @GeneratedValue(strategy=GenerationType.AUTO)// 表示主键值生成的方式策略是什么
    @Id
    public Integer getId() {
        return id;
    }
    
    ....
}

strategy 属性需要一个javax.persistence.GenerationType(枚举)

在 javax.persistence.GenerationType 中定义了以下几种可供选择的策略

  1. IDENTITY:采用数据库 ID自增长的方式来自增主键字段。

    • MySql 可以使用这种方式,其他数据库看具体情况,有的支持有的不支持。

    • Oracle可以支持这种方式,需要Oracle版本支持 identity ,同时配置文件的数据库方言需要正确配置,就会出现下面的建表语句。

      create table test(
          id number(19,0) generated as identity,
          name varchar2(80),
          primary key (id)
      )
      
  2. AUTO: JPA自动选择合适的策略,是默认选项。如果不写属性默认就是这个策略。他会自动的侦测数据表的情况来决定使用哪个主键生成策略。例如:

    • 比如说数据库是mysql的情况,他就会自动的使用主键自增的方式进行生成主键。
    • 比如说数据库是Oracle的情况,并且配置文件配置了自动建表的情况下,在程序启动的时候会创建一个序列 create sequence hibernate_sequence start with 1 increment by 1;他就会以这个序列的方式进行主键的生成。
      • 注意:如果使用了Oracle数据库,并且使用AUTO的方式生成主键,在运行的时候报错,可能是手动建表,并没有创建名字为 hibernate_sequence 的序列。
  3. SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式

    import  javax.persistence.*;
    
    @Table(name="jpa_cutomers") // 表示这个实体类映射的是数据库中的 jpa_cutomers 表
    @Entity
    public class Customer {
        
        @SequenceGenerator(name="sequenceGenerator", sequenceName="camera_info_id_seq", allocationSize=1)
        @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="sequenceGenerator")
        @Id
        private Integer id;    //generator="sequenceGenerator" 要和 @SequenceGenerator 的name一样
        
    }
    
  4. TABLE:通过表产生主键,选择这个属性需要配合@TableGenerator注解一起使用,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植

1.5、@TableGenerator

将当前主键的值单独保存到一个数据库的表中,主键的值每次都是从指定的表中查询来获得。(也就是说这个注解的作用是基于数据库表的方式生成ID主键)

这种方法生成主键的策略可以适用于任何数据库,不必担心不同数据库不兼容造成的问题。

要配合着@GeneratedValue和@Id一起使用。

代码例子:

import  javax.persistence.*;

@Table(name="jpa_cutomers") // 表示这个实体类映射的是数据库中的 jpa_cutomers 表
@Entity
public class Customer {
    
    private Integer id;
    ......

    // 可以先在数据库里创建一张名为 "jpa_id_generators" 的表
    /**CREATE TABLE `jpa_id_generators` (
     *   `id` int(11) NOT NULL AUTO_INCREMENT,
     *   `pk_name` varchar(50) NOT NULL COMMENT '主键的名字',
     *   `pk_value` int(11) NOT NULL,
     *   `comment` varchar(128) DEFAULT NULL COMMENT '备注',
     *   PRIMARY KEY (`id`)
     * ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
     */

    @TableGenerator(name="ID_GENERATOR",  //生成器名称
                    table="jpa_id_generators",        //生成器所用的表名称
                    pkColumnName="pk_name",           
                    pkColumnValue="customer_id",      // 找jpa_id_generators表中pk_name='customer_id'的那一行;select * from jpa_id_generators where pk_name='customer_id'
                    valueColumnName="pk_value",       // 用这个pk_value这一列的内容作为id的基数
                    allocationSize=100)               // 每次根据基数涨100
    @GeneratedValue(strategy=GenerationType.TABLE,generator="ID_GENERATOR")//这些注解可以在getter方法上加。。而且视频推荐在getter上加
    @Id
    public Integer getId() {
        return id;
    }
    /**
     * 其实关键点就在pkColumnName、pkColumnValue、valueColumnName这3个属性;
     * 这3个属性使用后会出现这个sql --> select pk_value from jpa_id_generators where pk_name = 'customer_id'。
     * 可以看到它的做法就是去jpa_id_generators表里找 pk_name 这一列 = 'customer_id' 的那一行记录,
     * 然后使用 pk_value 这以列的值作为生成当前主键id值的基础。
     * 
     * 这东西一般很少用,在一些特殊的情况下可能会用到
     */
    ......
}

有以下属性

  • name属性:表示该表主键生成策略的名称(也叫生成器名称),它被引用在@GeneratedValue中设置的“generator”值中。

  • table属性:表示要使用数据库中哪张表为当前主键生成提供支持。例如:我们数据库中有一张表叫 tb_generator 然后主键的生成就依靠这张表中的信息

  • catalog属性和schema属性:具体指定表所在的目录名或是数据库名。

  • pkColumnName属性:表示用表中的哪一列。例如在“tb_generator”中将“gen_name”这一列作为主键的键值

  • pkColumnValue属性:它的值表示在持久化表中,该生成策略所对应的主键。例如在“tb_generator”表中,将“gen_name”这一列中的值为“CUSTOMER_PK”的这一行记录作为当前生成ID主键所用的内容信息。

  • valueColumnName属性:它的值表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加。例如,在“tb_generator”中将“gen_value”作为主键的值

  • initialValue:表示主键初识值,默认为0。

  • allocationSize:表示每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50。

注意

其实关键点就在pkColumnName、pkColumnValue、valueColumnName这3个属性;这3个属性使用后会出现这个sql --> select pk_value from jpa_id_generators where pk_name = ‘customer_id’。可以看到它的做法就是去jpa_id_generators表里找pk_name 这一列 = ‘customer_id’ 的那一行记录,然后使用 pk_value 这以列的值作为生成当前主键id值的基础。

觉得会有一些问题,如果有 pkColumnValue 出现不唯一的情况会有问题吧;就算将这个设为唯一,并发的情况下也会出问题的吧

1.6、@Basic

表示一个简单的属性到数据库表的字段的映射,对于没有任何标注的 getXxxx() 方法,默认即为@Basic(意思是说:没有任何注解的字段和getter方法相当于默认加上@Basic,并且将这个java的字段名默认为数据库表的字段名,而且一切属性都是默认的。比如字段长度是默认的)

fetch 属性 :表示该属性的读取策略,有 EAGER 和 LAZY 两种,分别表示主支抓取和延迟加载,默认为 EAGER

optional 属性:表示该属性是否允许为null, 默认为true

1.7、@Column

当实体的属性(java字段)与其映射的数据库表的列不同名时需要使用

可与 @Id 标注一起使用

name 属性: 表示这个java字段和数据库的那个列作为映射关系 (常用属性)

unique 属性:唯一约束,默认为false

nullable属性:非空约束,写true表示可以为空;写false表示这个字段不能为空。默认true

insertable属性:持久化提供程序生成的SQL insert语句中是否包含该列。默认true。表示在使用“insert”脚本插入数据时,是否需要插入该字段的值。

updatable属性:持久化提供程序生成的SQL update语句中是否包含该列。默认true。表示在使用“update”脚本插入数据时,是否需要更新该字段的值。
一般实体类中的只读的属性就可以将 insertable 和 updatable 属性设为false,例如主键和外键等。这些字段的值通常是自动生成的。

length 属性:数据库表字段长度。当字段的类型为varchar时,该属性才有效,默认为255个字符
例如:

@Column(name="last_name",length=50,nullable=false)
private String lastName;

table属性:(可选)当前这个列所在的表名。如果不存在,则假定列在主表中。可以不写的。

precision 属性:数字精度相关的属性

scale 属性:数字精度相关的属性

@Column(name="amot",precision=4,scale=2)
private java.math.BigDecimal amot;
/**
 * precision = 4,scale = 2 这两个属性需要组合着使用;
 *     precision 的值要比 scale 的值大。
 * 解释:
 *     scale = 2 表示保存小数点后2为。就算你的小数点后面有 20 位,他还是只保存2位
 *     precision = 4 表示保存到数据库中,这个值算上小数点后面的数字只能保存4个数字的值。
 *     由于 scale 设置了 2 ,整数部分就只能是2位数了。整数部分多于2位数就会报错。
 * 总结:
 *     precision表示数值的总长度,scale表示小数点所占的位数。(小数点后面有多少位都没有关系,保存数据时小数点后面的部分只会最多保存 scale 表示的位数。)
 *     这是基于Oracle测试得到的结论,mysql等其他的应该也是一样
 */

columnDefinition 属性:表示该字段在数据库中的实际类型.通常 ORM 框架可以根据属性类型自动判断数据库中字段的类型,但是对于Date类型仍无法确定数据库中字段类型究竟是DATE,TIME还是TIMESTAMP。此外,String的默认映射类型为VARCHAR,如果要将 String 类型映射到特定数据库的 BLOB 或TEXT 字段类型(用于精确地设置java字段对应的数据库类型)
例如:

@Column(name = "text_name", columnDefinition = "varchar(50) not null")
private String textName; // 字段“text”,指定建表时SQL语句 如“varchar(50) NOT NULL”
/**
 * 等同于SQL
 * CREATE TABLE table_name (
 *   id integer not null,
 *   text_name varchar(50) NOT NULL ,
 *   primary key (id)
 *  )
 */

@Column(name="text_content",columnDefinition="clob not null")
private Clob textContent;
/**
 * 等同于SQL
 * CREATE TABLE table_name (
 *   id integer not null,
 *   text_content clob (200) not null,
 *   primary key (id)
 * )
 */

不加这个 @Column 注解,就默认用java字段对应数据库的字段

1.8、@Transient

表示该属性(java字段)并非一个到数据库表的字段的映射,ORM框架将忽略该属性。(就是说这个getter方法不想让他作为数据库的一列就加上@Transient)

如果一个属性(java字段)并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic

1.9、@Temporal

在核心的 Java API 中并没有定义 Date 类型的精度(temporal precision)。而在数据库中,表示 Date 类型的数据有 DATE, TIME 和 TIMESTAMP 三种精度(即单纯的日期,时间,或者两者 兼备)。在进行属性映射时可使用@Temporal注解来调整精度.

  • 如果是java属性是Date类型 ,数据库的表默认就会是datetime类型 年月日时分秒的时间格式。

  • 在java字段上加上@Temporal(TemporalType.DATE) 就是 年月日格式 数据库中数据类型就是date

  • 在java字段上加上@Temporal(TemporalType.TIME) 就是 时分秒格式 数据库中数据类型就是time

  • 在java字段上加上@Temporal(TemporalType.TIMESTAMP) 就是 年月日时分秒格式 数据库中数据类型就是timestamp

例如:

@Column(name="create_time")
@Temporal(TemporalType.TIMESTAMP)
public Date getCreateTime() {
	return createTime;
}

2、常用的一些API(接口和类)

2.1、Persistence类

主要是是用于获取 EntityManagerFactory 实例。

这里要介绍的函数如下:

2.1.1、createEntityManagerFactory(…)

该类包含一个名为 createEntityManagerFactory静态方法 。Persistence类对createEntityManagerFactory 做了重载。所以 createEntityManagerFactory 方法是有两个。

  1. Persistence.createEntityManagerFactory("persistence.xml里面的持久化单元的名字")

    • 带有一个参数的方法以 JPA 配置文件 persistence.xml 中的持久化单元名为参数
  2. Persistence.createEntityManagerFactory("persistence.xml里面的持久化单元的名字",Map 配置参数)

    • 带有两个参数的方法:前一个参数含义相同,后一个参数 Map类型,用于设置 JPA 的相关属性,这时将忽略其它地方设置的属性。Map 对象的属性名必须是 JPA 实现库提供商的名字空间约定的属性名。
import javax.persistence.Persistence;
public class TestMain{
    public static void main( String[] args ){
        //这个就是persistence.xml里面的持久化单元的名字   就是<persistence-unit>节点的name属性
        String persistenceUnitName = "jpa-1"; 
        
        //方式1;  
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName);
    
    	/**
    	 * 方式2
    	 * Map<String,Object> properites = new HashMap<String,Object>();
    	 * 
    	 * properites.put("hibernate.show_sql",false); 这里写的是persistence.xml里面的<properites>节点的配置。
    	 * 
    	 * Persistence.createEntityManagerFactory(persistenceUnitName,properites);//将持久化单元的名字和配置给到这个方法,就能获取到EntityManagerFactory
    	 */
        
        。。。。。。。
    }
}

2.2、EntityManagerFactory接口

主要用来创建 EntityManager 实例。(跟hibernate的sessionFactory差不多)

这里要介绍下面这4个函数:

2.2.1、createEntityManager ()

createEntityManager ():用于创建实体管理器对象实例。(类似于 Hibernate 的 SessionFactory 获取 session)

EntityManager entityManager = entityManagerFactory.createEntityManager();

2.2.2、createEntityManager(…)

createEntityManager(Map map):用于创建实体管理器对象实例的重载方法,Map 参数用于提供 EntityManager 的属性

2.2.3、isOpen()

isOpen():检查 EntityManagerFactory 是否处于打开状态。实体管理器工厂创建后一直处于打开状态,除非调用close()方法将其关闭

2.2.4、close()

close():关闭 EntityManagerFactory 。 EntityManagerFactory 关闭后将释放所有资源,isOpen()方法测试将返回 false,其它方法将不能调用,否则将导致IllegalStateException异常

重要的就是 createEntityManager () 和close()

2.3、EntityManager类

在 JPA 规范中, EntityManager 是完成持久化操作的核心对象。

EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。

实体作为普通 Java 对象,只有在调用 EntityManager 将其持久化后才会变成持久化对象。它还可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。

简单介绍一下实体的状态:

和hibernate类似,不懂可以去看看hibernate的知识

新建状态: 新创建的对象,尚未拥有持久性主键

持久化状态:已经拥有持久性主键并和持久化建立了上下文环境

游离状态拥有持久化主键,但是没有与持久化建立上下文环

删除状态: 拥有持久化主键,已经和持久化建立上下文环境,但是从数据库中删除

详细介绍在下面[JPA 中实体类对象状态介绍和转换](#四、JPA 中实体类对象状态介绍和转换)

它的一些方法:

2.3.1、find(…)

find (Class<T> entityClass, Object primaryKey):第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值

这个方法的作用是:返回指定的 OID 对应的实体类对象,如果这个实体存在于当前的持久化环境,则返回一个被缓存的对象;否则会创建一个新的 Entity, 并加载数据库中相关信息;若 OID 不存在于数据库中,则返回一个 null。

类似于 hibernate 中 Session 的 get 方法

package com.xxx.jpademo.jpademo01;

import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    
    @Test
	public void testFind(){
        // (类似于 hibernate 中 Session 的 get 方法)
		Customer customer = entityManager.find(Customer.class, 1);
		
		System.out.println("-----------------");
		System.out.println(customer);
	}
}
2.3.2、getReference(…)

getReference (Class<T> entityClass,Object primaryKey):第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值

这个方法的作用是:与find()方法类似,不同的是:如果缓存中不存在指定的 Entity,EntityManager 会创建一个 Entity 类的代理,但是不会立即加载数据库中的信息(不会发sql查询),只有第一次真正使用此 Entity 的属性才加载数据库中的数据(发sql),所以如果此 OID 在数据库不存在,getReference() 不会返回 null 值, 而是抛出EntityNotFoundException

类似于 hibernate 中 Session 的 load 方法

public class JPATest {
    
    // 这里省略 entityManager 相关的代码。看上面的就可以了
    
    @Test
    public void testGetReference(){
        Customer customer = entityManager.getReference(Customer.class, 1);

        System.out.println("-----------------");
        System.out.println(customer.getClass());// 可以看到他就是一个代理对象

        // 只有第一次真正使用此 Entity 的属性才加载数据库中的数据(发sql)
        // 这个类重写了toString方法,里面就会第一次调用到这个Entity的属性,这时候才真正的发送sql
        System.out.println(customer);
    }
}

注意

find()和getReference()都是通过ID获取一个对象。发送一个select语句查询

两者的区别是

  1. find是立即查询(立即检索),getReference用的是一个延迟加载,用的时候就加载(延迟检索)。getReference()得到的对象是一个代理对象,在控制台不发sql语句了;只要使用对象里面的属性将会执行sql,执行getReference()方法,如果不使用该对象,则不会立即执行查询操作,而返回一个代理对象 。(将getReference()返回的对象放入System.out.println(customer) ,如果对象里写了toString(),就表示使用对象,这时就会执行SQL)

  2. 在用 getReference() 做查询会可能出现懒加载异常。如果在没用到查询的结果之前就把entityManager关掉了。就会出异常:LazyInitializationException。。因为getReference()只是查询出一个代理对象,还没执行sql查询呢。entityManager就关了,就出现异常了。

  3. 如果数据库中没有对应的记录find()会返回null;而getReference()会抛异常 EntityNotFoundException;例如:getReference(Customer.class, 144L); 数据库中没有144的记录,就会抛出异常。

若数据表中没有对应记录,且entityManager也没有关闭,同时需要使用对象时。find() 会返回null getReference会抛异常 (如果查询出来的代理对象没用被使用,即使关了entityManager是不会出异常的)

2.3.3、persist(…)

persist (Object entity):用于将新创建的 Entity 纳入到 EntityManager 的管理。该方法执行后,传入 persist() 方法的 Entity 对象转换成持久化状态。(也就是会发送 insert 语句保存数据到数据库)

  • 如果传入 persist() 方法的 Entity 对象已经处于持久化状态,则 persist() 方法什么都不做。

  • 如果对删除状态的 Entity 进行 persist() 操作,会转换为持久化状态。

  • 如果对游离状态的实体执行 persist() 操作,可能会在 persist() 方法抛出 EntityExistException(也有可能是在flush或事务提交后抛出)。

public class JPATest {
    
    // 这里省略 entityManager 相关的代码。看上面的就可以了
    
    //类似于 hibernate 的 save() 方法. 使对象由临时状态变为持久化状态. 
	//和 hibernate 的 save 方法的不同之处: 若对象有 id, 则不能执行 insert 操作, 而会抛出异常. 
	@Test
	public void testPersistence(){
		Customer customer = new Customer(null,"BB","bb@163.com",15,new Date(),new Date());

		/**
		 * 若对象有 id, 则不能执行 insert 操作, 而会抛出异常.  而hibernate的save()则不会。
		 * 
		 * 在hibernate的save()方法之前设置ID是无效的。配置了ID的策略 在这个位置写这个是没用的。
		 */
        
		//customer.setId(100);  若给对象设置 id, 则不能执行 insert 操作, 而会抛出异常.
			
		entityManager.persist(customer);//临时状态变为持久化状态了
		System.out.println(customer.getId());
	}
}
2.3.4、remove (…)

remove (Object entity):删除实例。如果实例是被管理的,即与数据库实体记录关联,则同时会删除关联的数据库记录。

注意: 该方法只能移除持久化对象, hibernate 的 delete 方法实际上还可以移除 游离对象.

public class JPATest {
    
    // 这里省略 entityManager 相关的代码。看上面的就可以了
    
    /**
	 * 类似于 hibernate 中 Session 的 delete 方法. 把对象对应的记录从数据库中移除
	 * 
	 * 注意: 该方法只能移除 持久化 对象. 而 hibernate 的 delete 方法实际上还可以移除 游离对象.
	 */
	@Test
	public void testRemove(){
		//Customer customer = new Customer();
		//customer.setId(2);    remove()方法只能移除 持久化 对象. 不能自己造一个对象去删除数据库的数据
		
		Customer customer = entityManager.find(Customer.class, 2);
		entityManager.remove(customer);
	}
   
}
2.3.5、merge(…)

merge (T entity) :用于处理 Entity 的同步。即数据库的插入和更新操作。(类似于 hibernate Session 的 saveOrUpdate 方法.)

在这里插入图片描述

public class JPATest {
    
    // 这里省略 entityManager 相关的代码。看上面的就可以了
    
    @Test
	public void testMerge1(){
		/**
		 * 1. 若传入的是一个临时对象    会复制这个传进去对象的内容到新的对象中
		 *    会创建一个新的对象, 把临时对象的属性复制到新的对象中, 然后对新的对象执行持久化操作. 所以新的对象中有 id, 但以前的临时对象中没有 id. 
		 */
		Customer customer = new Customer(null,"CC","cc@163.com",18,new Date(),new Date());
		
		//会复制这个传进去对象的内容到新的对象中,然后将复制的新对象进行insert,然后返回这个复制的新对象
		Customer customer2 = entityManager.merge(customer);
		
		//会创建一个新的对象, 把临时对象的属性复制到新的对象中, 然后对新的对象执行持久化操作. 所以新的对象中有 id, 但以前的临时对象中没有 id.
		System.out.println("customer#id:" + customer.getId());
		System.out.println("customer2#id:" + customer2.getId());//会复制传进去对象的内容到新的对象中   customer2会有ID; customer没有ID
	}
	
	@Test
	public void testMerge2(){
		/**
		 * 若传入的是一个游离对象(或者new一个临时对象并赋值ID充当游离对象),EntityManager 缓存中没有该对象,数据库中也没有对应的记录
		 * 
		 * 也是会进行insert;
		 * 其过程呢:会(发送select语句)查询对应的记录,发现数据库没有; 会复制这个传进去对象的内容到新的对象中,然后将复制的新对象进行insert,然后返回这个复制的新对象
		 * 
		 * 若传入的是一个游离对象, 即传入的对象有 OID. 
		 * 1. 若在 EntityManager 缓存中没有该对象
		 * 2. 若在数据库中也没有对应的记录
		 * 3. JPA 会创建一个新的对象, 然后把当前游离对象的属性复制到新创建的对象中
		 * 4. 对新创建的对象执行 insert 操作. 
		 */
		Customer customer = new Customer(null,"DD","dd@163.com",18,new Date(),new Date());
		
		customer.setId(320);//320在数据库中没有, EntityManager 缓存中也没有
		
		Customer customer2 = entityManager.merge(customer);
		
		System.out.println("customer#id:" + customer.getId());
		System.out.println("customer2#id:" + customer2.getId());// 设置的320号ID是没有作用的。会返回id生成策略所得到的那个ID
	}
	
	@Test
	public void testMerge3(){
		/**
		 * 若传入的是一个游离对象(或者new一个临时对象并赋值ID充当游离对象),EntityManager 缓存中没有该对象,但是数据库中有这个ID的数据
		 * 将会进行update
		 * 其过程呢: 会(发送select语句)查询对应的记录, 然后返回该记录对应的对象, 再然后会把游离对象的属性复制到查询到的对象中.
		 * 然后进行update
		 * 
		 * 
		 * 
		 * 若传入的是一个游离对象, 即传入的对象有 OID. 
		 * 1. 若在 EntityManager 缓存中没有该对象
		 * 2. 若在数据库中也有对应的记录
		 * 3. JPA 会查询对应的记录, 然后返回该记录对一个的对象, 再然后会把游离对象的属性复制到查询到的对象中.
		 * 4. 对查询到的对象执行 update 操作. 
		 */
		Customer customer = new Customer(null,"EE-update","ee@163.com",18,new Date(),new Date());
		customer.setId(100);
		
		Customer customer2 = entityManager.merge(customer);
		
		System.out.println(customer == customer2); //false
	}
		
	@Test
	public void testMerge4(){
		/**
		 * 传入的是一个游离对象(或者new一个临时对象并赋值ID充当游离对象),在 EntityManager 缓存中有对应的对象(用find()查询出来)
		 * 将会进行update
		 * 其过程呢:JPA会把游离对象(临时对象)的属性复制到查询到EntityManager 缓存中的对象中(用find()查询出来的那个对象中).
		 * 然后用EntityManager 缓存中的对象执行 UPDATE. 
		 * 
		 * 
		 * 若传入的是一个游离对象, 即传入的对象有 OID. 
		 * 1. 若在 EntityManager 缓存中有对应的对象
		 * 2. JPA 会把游离对象的属性复制到查询到EntityManager 缓存中的对象中.
		 * 3. EntityManager 缓存中的对象执行 UPDATE. 
		 */                 
		Customer customer = new Customer(null,"EE-test-update","ee@163.com",18,new Date(),new Date());
		customer.setId(100);
		
		Customer customer2 = entityManager.find(Customer.class, 100);
		
		entityManager.merge(customer);
		
		/**
		 * 这里就和hibernate有些区别了
		 *     在同一个entityManager 有了2个同为id是100的对象,这时在JPA中是可以的因为他有个复制操作;
		 *     在hibernate是不允许的,不允许在同一个时刻同一个session里有两个OID一样的对象
		 *     
		 *     在hibernate中已经将ID为100的对象查询了出来,已经将OID为100的对象和session关联上了,
		 *     下面的保存又一次将ID为100的游离对象加入到session中是会报错的
		 *     
		 *     在jpa中有一个复制操作所以不会报错,将游离对象的内容复制到查询出来的对象中,这就不报错了
		 *     
		 */
		
		System.out.println(customer == customer2); //false
	}
}

说明:

  • 第一种情况: 传入的是一个临时对象当然是insert;其过程呢: 会复制这个传进去对象的内容到新的对象中,然后将复制的新对象进行insert,然后返回这个复制的新对象

  • 第二种情况:若传入的是一个游离对象(或者new一个临时对象并赋值ID充当游离对象),EntityManager 缓存中没有该对象,数据库中也没有对应的记录,也是会进行insert;其过程呢: 会(发送select语句)查询对应的记录,发现数据库没有;会复制这个传进去对象的内容到新的对象中,然后将复制的新对象进行insert,然后返回这个复制的新对象

  • 第三种情况:若传入的是一个游离对象(或者new一个临时对象并赋值ID充当游离对象),EntityManager 缓存中没有该对象,但是数据库中有这个ID的数据,将会进行update。其过程呢: 会(发送select语句)查询对应的记录, 然后返回该记录对应的对象, 再然后会把游离对象的属性复制到查询到的对象中,然后进行update

  • 第四种情况:传入的是一个游离对象(或者new一个临时对象并赋值ID充当游离对象),在 EntityManager 缓存中有对应的对象(用find()查询出来),将会进行update;其过程呢:JPA会把游离对象(临时对象)的属性复制到查询到EntityManager 缓存中的对象中(用find()查询出来的那个对象中)然后用EntityManager 缓存中的对象执行 UPDATE.

2.3.6、flush ()

同步持久上下文环境,就是将持久上下文环境的所有未保存实体的状态信息保存到数据库中。(更改了持久化状态对象的属性,再调用 flush() ,会强制发送update sql语句,使数据表的记录跟内存中对象数据保持一致,不改持久化状态对象的属性,就算你调用 flush() ,也是不会发送 update 的)。调用这个方法的目的就是为了使数据库中的数据要和当前的对象数据一样

public class JPATest {
    
    // 这里省略 entityManager 相关的代码。看上面的就可以了
    
   @Test
	public void testFlush(){
		Customer customer = entityManager.find(Customer.class, 100);
		System.out.println(customer);
		
		/**
		 * 跟hibernate的flush()一样
		 * 
		 * 这里查询出数据库的记录并放入缓存中,然而你在这儿改了一个属性值,在后面的提交事务前 transaction.commit();之前 会走一个entityManager.flush()的方法
		 * 就会检查到缓存中的数据和数据库中不一样了,这时会发送update语句(只是发送语句),直到真正提交事务就会把数据库数据进行更新。让session缓存和数据库保持一致
		 * 
		 * 在某些场景下当然也可以把entityManager.flush()拿出来单独使用。
		 * 
		 * 如果记录ID是由底层数据库自增的方式生成的,则在调用persist()方法时,(不用到flush),立即发送insert语句
		 * 因为persist方法后,必须保证对象的ID是存在的。  
		 * 调用persist()方法后这个id是存在的而且是有效的,为了得到这个ID只能去执行insert语句才能知道ID是几,但是事务并未提交就是为了拿ID
		 * 如果id是hibernate生成的话,你会看到调 transaction.commit();方法的时候去发送对应的sql语句 (在*.hbm.xml)文件中配置ID的生成方式
		 * 
		 */
		customer.setLastName("TEST-FLUSH");//会在后面提交事务之前发现你改了这个对象会执行update语句。进行更新记录。
		
		//显式在这里调用flush()将会发送update的语句。但是事务没有提交。。直到后面提交事务后才更新数据库记录
		entityManager.flush();
	}
	
}

从上面的介绍可以看到,以后我们想要修改某个数据表的数据时,直接改实体类的属性就好。因为在 transaction.commit(); 的方法里面会有一个 flush () 方法强行发送update 的 sql 语句,使数据库中的数据和当前的对象数据保持一致。

经过测试只要将属性改了再调用 flush() 就会发送update;你不改属性就调用 flush() 是不会发update的

2.3.7、refresh(…)

refresh (Object entity):用数据库实体记录的值更新实体对象的状态,即更新实例的属性值。

会强制发送select语句,以使缓存中对象的状态和数据表中对应的记录保持一致。

public class JPATest {
    
    // 这里省略 entityManager 相关的代码。看上面的就可以了
    
    @Test
	public void testRefresh() {
		Customer customer = (Customer) entityManager.find(Customer.class, 100);
		System.out.println(customer);

        /** 
         * 强行的又发送一个查询id为100的sql语句。相当于重新获取一下这个 customer 对象,说白了就是看看数据库数据是不是最新的,
         * 将数据库中的数据信息重新加载到 entityManager 缓存中。(也许数据库被强制修改了就用这个让他保持一致)
         */
		entityManager.refresh(customer);
		
		System.out.println(customer);
	}
  
    @Test
	public void testRefresh02(){
		Customer customer = entityManager.find(Customer.class, 1);
		customer = entityManager.find(Customer.class, 1);
		
		entityManager.refresh(customer);//本来可以发送一个sql,这里加了refresh()方法 强制发送了一个sql语句。也许数据库被强制修改了就用这个让他保持一致
	}
	
}

简单理解: flush() 方法是强行发送 update 让数据库中的数据和java对象数据一样。refresh(Object entity)方法是强行发送 select 让java对象的数据和数据库表里的数据一样

2.3.8、setFlushMode(…)

setFlushMode (FlushModeType flushMode):设置持久上下文环境的Flush模式。

参数可以取2个枚举

  • FlushModeType.AUTO:为自动更新数据库实体,

  • FlushModeType.COMMIT:为直到提交事务时才更新数据库记录

2.3.9、getFlushMode()

getFlushMode ():获取持久上下文环境的Flush模式。返回FlushModeType类的枚举值。

2.3.10、clear()

clear ():清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。

public class JPATest {
    
    // 这里省略 entityManager 相关的代码。看上面的就可以了
    
    /**
	 * clear() 清理缓存
	 */
	@Test
	public void testClear() {
		Customer customer = (Customer) entityManager.find(Customer.class, 100);
		System.out.println(customer);
		
		entityManager.clear();//清理缓存 会发送两次sql语句
		
		Customer customer1 = (Customer) entityManager.find(Customer.class, 100);
		System.out.println(customer1);
	}
}
2.3.11、detach(…)

detach(Object entity):方法将实体从持久化上下文中移除,变成detach(游离)状态。对某个持久化状态的对象使用这个方法后,再对这个实体的属性进行更改, 是不会写入到数据库的。

public class JPATest {
    
    // 这里省略 entityManager 相关的代码。看上面的就可以了
    
	@Test
	public void detach() {
		Usr usr = entityManager.find(Usr.class, 1);
		
		entityManager.detach(usr); //分离对象
		Assertions.assertFalse(entityManager.contains(usr));//对象不包含在实体管理器中
		usr.setName("刘备2"); //对象值更新不会同步到数据库
        
	}
}
2.3.12、contains(…)

contains (Object entity):判断一个实例是否属于当前持久上下文环境管理的实体。

2.3.13、isOpen ()

isOpen ():判断当前的实体管理器是否是打开状态

2.3.14、getTransaction ()

getTransaction ():返回资源层的事务对象。EntityTransaction实例可以用于开始和提交多个事务。

2.3.15、close ()

close ():关闭实体管理器。之后若调用实体管理器实例的方法或其派生的查询对象的方法都将抛出 IllegalstateException 异常,除了getTransaction 和 isOpen方法(返回 false)。不过,当与实体管理器关联的事务处于活动状态时,调用 close 方法后持久上下文将仍处于被管理状态,直到事务完成。

2.3.16、createQuery (…)

createQuery (String qlString):创建一个查询对象。

2.3.17、createNamedQuery (…)

createNamedQuery (String name):根据命名的查询语句块创建查询对象。参数为命名的查询语句

2.3.18、createNativeQuery (…)

createNativeQuery (String sqlString):使用标准 SQL语句创建查询对象。参数为标准SQL语句字符串

createNativeQuery (String sqls, String resultSetMapping):使用标准SQL语句创建查询对象,并指定返回结果集 Map的 名称

2.3、EntityTransaction接口

用来管理资源层实体管理器的事务操作。通过调用实体管理器的 getTransaction方法 获得其实例。(就是JPA的事务管理)

它的一些方法

1、begin ()

用于启动一个事务,此后的多个数据库操作将作为整体被提交或撤消。若这时事务已启动则会抛出 IllegalStateException 异常。

2、commit ()

用于提交当前事务。即将事务启动以后的所有数据库更新操作持久化至数据库中。

3、rollback ()

撤消(回滚)当前事务。即撤消事务启动后的所有数据库更新操作,从而不对数据库产生影响。

4、setRollbackOnly ()

使当前事务只能被撤消。

5、getRollbackOnly ()

查看当前事务是否设置了只能撤消标志。

6、isActive ()

查看当前事务是否是活动的。如果返回true则不能调用begin方法,否则将抛出 IllegalStateException 异常;如果返回 false 则不能调用 commit、rollback、setRollbackOnly 及 getRollbackOnly 方法,否则将抛出 IllegalStateException 异常。

2.4、Query接口

Query接口封装了执行数据库查询的相关方法(这个Query接口跟hibernate接口很类似)。

2.4.1、获取 Query 接口的实例对象

调用 EntityManager 的 createQuery(…)createNamedQuery(…)createNativeQuery(…) 方法可以获得 Query 接口的实例对象,进而可调用 Query 接口的相关方法来执行数据库操作。需要注意的是这几个方法有多个重载,可以根据不同的需要来获取 Query 接口的实例对象。这几个重载的方法就不在这里列出来了。

下面是 Query 接口的一些方法的介绍

2.4.2、executeUpdate()

int executeUpdate() :用于执行 update 或 delete 语句。

import javax.persistence.*

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
    
    @After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    
   //可以使用 JPQL 完成 UPDATE 和 DELETE 操作. 
	@Test
	public void testExecuteUpdate(){
		/**
		 * update语句用于执行数据更新操作。主要用于针对单个实体类的批量更新
		 */
		String jpql = "UPDATE Customer c SET c.lastName = ? WHERE c.id = ?";
		Query query = entityManager.createQuery(jpql).setParameter(1, "YYY").setParameter(2, 12);
		
		query.executeUpdate();
	}
	//可以使用 JPQL 完成 UPDATE 和 DELETE 操作. 
	@Test
	public void testExecuteDelete(){
		/**
		 * delete语句用于执行数据更新操作。
		 * 以下语句删除不活跃的、没有订单的客户
		 *     DELETE FROM Customer c where c.lastName = 'inactive' and c.orders is empty
		 */
		//String jpql = "DELETE FROM Customer c where c.lastName = 'inactive' and c.orders is empty";//实体类中没有写关联order 
		String jpql = "DELETE FROM Customer c where c.lastName = 'inactive'";
		Query query = entityManager.createQuery(jpql);
		
		query.executeUpdate();
	}
}
2.4.3、getResultList()

List getResultList() :用于执行select语句并返回结果集实体列表

import javax.persistence.*

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
    
    @After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    @Test
    public void testHelloJPQL(){
        //跟hibernate的HQL很像
        String jpql = "FROM Customer c WHERE c.age > ?";
        Query query = entityManager.createQuery(jpql);

        //占位符的索引是从 1 开始
        query.setParameter(1, 1);
        List<Customer> customers = query.getResultList();
        System.out.println(customers.size());
    }
}
2.4.4、getSingleResult()

Object getSingleResult(): 用于执行只返回单个结果实体的select语句。使用这个方法如果查询不到结果会报一个NoResultException

import javax.persistence.*

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    
    
    @Test
	public void testQueryMaxId(){
    	Query query = entityManager.createQuery("select max(c.id) from Customer c");

    	Object result = query.getSingleResult();// 获取查询到的单个结果

    	Integer max = (Integer)result;
    	
    	System.out.println(max);
	}
    
    @Test
	public void testQuerySingleResult(){
    	Query query = entityManager.createQuery("FROM Customer c WHERE c.id = ?");
    	
    	query.setParameter(1, 1); //给JPQL语句的占位符赋值,占位符的索引是从 1 开始

    	Object result = query.getSingleResult(); // 获取查询到的单个结果

    	Customer customer = (Customer)result;
    	
    	System.out.println(customer);
	}
}
2.4.5、unwrap()

T unwrap(Class<T> cls) 这个方法可以用于自定义返回结果的类型,还可以获取hibernate里面的 Session 等比较底层的一些对象用来操作一些特殊的操作。可能可以用于其他用途,不清楚

@Test
public void testUnwrap(){
    
    String sql = "select * from jpa_cutomers";
    Query query = entityManager.createNativeQuery(sql);
    // 如果我们直接查询这个sql语句会返回一个Object[]的数组,写了下面这行代码后,能将Object[]数组转为Map集合
	query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
	// 用了 unwrap()方法,定义了返回Map,这里的查询结果就会得到Map的集合。
    List<Map<String,Object>> customers = query.getResultList();
    System.out.println(customers.size());

    //新版改成下面这样了。因为新版把旧版的unwrap(SQLQuery.class)设为过期的方法了。
    // 在spring data jpa中还需要设置为只读事务(@Transactional(readOnly=true))才起作用,不然会报异常。说是一个代理类不能转为NativeQueryImpl
    //query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
}
2.4.6、setFirstResult(…)

Query setFirstResult(int startPosition) 用于设置从哪个实体记录开始返回查询结果(分页用到)

2.4.7、setMaxResults(…)

Query setMaxResults(int maxResult) 用于设置返回结果实体的最大数。与setFirstResult结合使用可实现分页查询(分页用到)

import java.util.List;

import javax.persistence.*

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    
    @Test
	public void testPageQuery() {
		
		Query query = entityManager.createQuery("FROM Customer");
		
		int pageNo = 3;   // 表示第3页
		int pageSize = 5; // 表示每页取5条
		
		/**
		 * 分页查询:
		 *      setFirstResult(int firstResult): 设定从哪一个对象开始检索, 参数 firstResult 表示这个对象在查询结果中的索引位置, 
		 *      索引位置的起始值为 0 。 默认情况下, Query 从查询结果中的第一个对象开始检索
		 *            
		 *      setMaxResults(int maxResults): 设定一次最多检索出的对象的数目。 
		 *      
         *      例如:
		 *      一共12条数据;
		 *      每页取5条
		 *      从第3页取
		 *      由于 参数 firstResult 起始值为 0 。所以要减1
		 *      
		 *      由于是12条记录,每页取5条,就只能最多分3页。所以下面的结果就是最后的2条记录
		 *      第一页 1-5       limit 0,5       从下标0开始取5条
		 *      第二页 6-10      limit 5,5       从下标5开始取5条
		 *      第三页 11-12     limit 10,5     从下标10开始取5条
		 *      
		 */
		List<Customer> list = query.setFirstResult((pageNo - 1) * pageSize)
									.setMaxResults(pageSize)
									.getResultList();
		
		System.out.println(list);
	}
    
}
2.4.8、setFlushMode(…)

Query setFlushMode(FlushModeType flushMode) 设置查询对象的Flush模式。

参数可以取2个枚举值:

  • FlushModeType.AUTO 为自动更新数据库记录,
  • FlushModeType.COMMIT 为直到提交事务时才更新数据库记录。
2.4.9、setHint(…)

setHint(String hintName, Object value) 设置与查询对象相关的特定供应商参数或提示信息。参数名及其取值需要参考特定 JPA 实现库提供商的文档。如果第二个参数无效将抛出IllegalArgumentException异常

/**
 * 使用 hibernate 的查询缓存. 
 *   前提是在配置文件中配置了启用查询缓存  <property name="hibernate.cache.use_query_cache" value="true"/>
 *   通过调用.setHint(QueryHints.HINT_CACHEABLE, true) 两句都要调用。告诉他要使用查询缓存
 * 
 */
@Test
public void testQueryCache(){
    String jpql = "FROM Customer c WHERE c.age > ?";
    Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);

    //占位符的索引是从 1 开始
    query.setParameter(1, 1);
    List<Customer> customers = query.getResultList();
    System.out.println(customers.size());

    query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);

    //占位符的索引是从 1 开始
    query.setParameter(1, 1);
    customers = query.getResultList();
    System.out.println(customers.size());
}
2.4.10、setParameter(…)

这三个方法是通过jpql的预编译参数的下标位置进行赋值的

  • setParameter(int position, Object value) 为查询语句的指定位置参数赋值。Position 指定参数序号,value 为赋给参数的值。

  • setParameter(int position, Date d, TemporalType type) 为查询语句的指定位置参数赋 Date 值。Position 指定参数序号,value 为赋给参数的值,temporalType 取 TemporalType 的枚举常量,包括 DATE、TIME 及 TIMESTAMP 三个,用于将 Java 的 Date 型值临时转换为数据库支持的日期时间类型(java.sql.Date、java.sql.Time及java.sql.Timestamp)

  • setParameter(int position, Calendar c, TemporalType type) 为查询语句的指定位置参数赋 Calendar值。position 指定参数序号,value 为赋给参数的值,temporalType 的含义及取舍同前

// 例如像这样
Query query = entityManager.createQuery( "select o from Orders o where o.id = ?1 and o.customer = ?2" );
query.setParameter( 1, 2 );
query.setParameter( 2, "John" );
List orders = query.getResultList();
......

这三个方法是通过jpql的命名参数进行赋值的

  • setParameter(String name, Object value) 为查询语句的指定名称参数赋值

  • setParameter(String name, Date d, TemporalType type) 为查询语句的指定名称参数赋 Date 值。用法同前

  • setParameter(String name, Calendar c, TemporalType type) 为查询语句的指定名称参数设置Calendar值。name为参数名,其它同前。该方法调用时如果参数位置或参数名不正确,或者所赋的参数值类型不匹配,将抛出 IllegalArgumentException 异常

// 例如像这样
Query query = entityManager.createQuery( "select o from Orders o where o.id = :myId and o.customer = :customerName" );
query.setParameter( "myId", 2 );
query.setParameter( "customerName", "John" );
List orders = query.getResultList();
......

四、JPA 中实体类对象状态介绍和转换

前面介绍了JPA是用于对象持久化的 API。实体类的实例作为普通 Java 对象,只有在使用了 JPA 的 API 才能进行持久化操作,JPA在对实体对象进行持久化操作的过程中分为了4个状态。

  • 临时/瞬时状态:使用 new 创建新对象,没有OID,一级缓存不存在,在数据库中没有对应的记录。就是一个新new的java对象,没有托管在JPA的EntityManager里面,跟JPA没有任何关系的java对象(规范的来说我们要创建临时状态的对象一般不要设置ID主键,ID主键会在你进行持久化后自动的得到。如果你一定要写也是可以,如果这个id在数据库中存在的话他其实算是游离态对象。)
  • 持久化状态:当前实体对象存在于持久化上下文中,也就是托管在JPA的EntityManager里面,有OID,存在于一级缓存中,数据库有对应的记录,这个状态下可以通过 JPA 的 API 操作数据的改变。简单理解就是:有一个对象有ID,存在于JPA的一级缓存中,和数据库的记录对应,能通过 JPA 的 API 操作数据的变更和删除
  • 游离状态:当前实体对象不存在于持久化上下文中,也就是没有托管在JPA的EntityManager里面,它不存在于一级缓存中,但是他也有OID,在数据有对应的记录。一般情况需下,游离状态对象是由持久化状态对象转变过来的,当然自己 new 一个对象然后赋值一个数据记录存在的 ID 主键也是能充当游离对象的。
  • 删除状态:当前实体对象在数据库没有对应的记录不存在于持久化上下文中,也就是没有托管在JPA的EntityManager里面,它不存在于一级缓存中。这个对象曾经在数据库里有过记录,曾经被EntityManager监管过,只不过是被JPA执行了删除的方法,让这个对象断掉了和EntityManager的关系,同时将对应的数据库记录给删除掉了。一般来说,删除状态的对象应该是由执行了 JPA 的删除方法得到的,删除状态的对象一般也不要在进行使用了,让它作为垃圾让GC回收吧。
  • 各状态的转换示意图如下:在这里插入图片描述

五、数据操作

通过JPA的来操作数据库数据

1、新增操作

2、修改操作

  • 在一个 JPA 事务里面,将一个持久化状态的对象直接更改对象的属性他就能在提交事务的时候自动地发送 update 语句去执行更新数据,因为 JPA 要保证持久化状态的对象要和数据库一样(也就是java对象数据要和数据库的数据一样)。

    import java.util.List;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    import javax.persistence.Query;
    
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    import com.xxx.jpademo.jpademo01.pojo.Customer;
    
    public class JPATest {
    
    	private EntityManagerFactory entityManagerFactory;
    	private EntityManager entityManager;
    	private EntityTransaction transaction;
    	
    	@Before
    	public void init(){
    		//1. 创建 EntitymanagerFactory
    		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
    		//2. 创建 EntityManager. 
    		entityManager = entityManagerFactory.createEntityManager();
    		//3. 开启事务
    		transaction = entityManager.getTransaction();
    		transaction.begin();
    	}
    	
    	@After
    	public void destroy(){
    		//5. 提交事务
    		transaction.commit();
    		//6. 关闭 EntityManager
    		entityManager.close();
    		//7. 关闭 EntityManagerFactory
    		entityManagerFactory.close();
    	}
        
        @Test
    	public void testFind(){
        	Customer customer = entityManager.find(Customer.class,4);
            // 直接修改查询出来的对象的属性就能执行 update 一定要在持久化状态下更改,也一定要在一个JPA事务内。
        	customer.setEmail("test1@update.com");
    	}
    }
    

    从源码的角度来看它能修改数据库数据的原理

    它在提交事务的时候,在transaction.commit();这行代码里面执行了一个flush()方法,强行让数据库数据和当前对象的数据一致。前面有对 flush() 方法的介绍,点击查看。Debug跟踪如下图所示。在这里插入图片描述

  • 可以使用EntityManager的merge(T entity)方法执行修改数据。前面有对 merge(…) 方法的介绍,点击查看。

  • 可以使用 jpql 的UPDATE语句来更新数据

    import java.util.List;
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    import javax.persistence.Query;
    
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    import com.xxx.jpademo.jpademo01.pojo.Customer;
    
    public class JPATest {
    
    	private EntityManagerFactory entityManagerFactory;
    	private EntityManager entityManager;
    	private EntityTransaction transaction;
    	
    	@Before
    	public void init(){
    		//1. 创建 EntitymanagerFactory
    		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
    		//2. 创建 EntityManager. 
    		entityManager = entityManagerFactory.createEntityManager();
    		//3. 开启事务
    		transaction = entityManager.getTransaction();
    		transaction.begin();
    	}
    	
    	@After
    	public void destroy(){
    		//5. 提交事务
    		transaction.commit();
    		//6. 关闭 EntityManager
    		entityManager.close();
    		//7. 关闭 EntityManagerFactory
    		entityManagerFactory.close();
    	}
    	
    	@Test
    	public void testExecuteUpdate(){
    	    /**
    		 * update语句用于执行数据更新操作。主要用于针对单个实体类的批量更新
    		 */
    	    String jpql = "UPDATE Customer c SET c.lastName = ? WHERE c.id = ?";//update一个类,set类里面的属性就好了
    	    Query query = entityManager.createQuery(jpql).setParameter(1, "YYY").setParameter(2, 12);
    
    	    query.executeUpdate();
    	}
    }
    

3、删除操作

  • 可以使用EntityManager的remove (Object entity)方法执行删除数据。前面有对 remove (…) 方法的介绍,点击查看。

  • 可以使用 jpql 的DELETE语句来删除数据

    import java.util.List;
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    import javax.persistence.Query;
    
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    import com.xxx.jpademo.jpademo01.pojo.Customer;
    
    public class JPATest {
    
    	private EntityManagerFactory entityManagerFactory;
    	private EntityManager entityManager;
    	private EntityTransaction transaction;
    	
    	@Before
    	public void init(){
    		//1. 创建 EntitymanagerFactory
    		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
    		//2. 创建 EntityManager. 
    		entityManager = entityManagerFactory.createEntityManager();
    		//3. 开启事务
    		transaction = entityManager.getTransaction();
    		transaction.begin();
    	}
    	
    	@After
    	public void destroy(){
    		//5. 提交事务
    		transaction.commit();
    		//6. 关闭 EntityManager
    		entityManager.close();
    		//7. 关闭 EntityManagerFactory
    		entityManagerFactory.close();
    	}
        @Test
        public void testExecuteDelete(){
            /**
             * delete语句用于执行数据更新操作。
             * 以下语句删除不活跃的、没有订单的客户
             *     DELETE FROM Customer c where c.lastName = 'inactive' and c.orders is empty
             */
            //String jpql = "DELETE FROM Customer c where c.lastName = 'inactive' and c.orders is empty";//实体类中没有写关联order 
            String jpql = "DELETE FROM Customer c where c.lastName = 'inactive'";
            Query query = entityManager.createQuery(jpql);
    
            query.executeUpdate();
        }
    }
    

4、关联操作

4.1、单向多对一

操作步骤:

  1. 创建工程:可以用eclipse创建JPA工程,new–>找到 JPA Project创建就行;也可以创建普通的java工程,也可用maven创建普通的java工程

  2. 加入依赖:maven的方式和上面Hello Word的依赖引入是一样的

  3. 编写persistence.xml文件

persistence.xml这个实际上是JPA最基本的配置文件,这里面会包含连接数据库的基本信息,会包含使用哪个ORM框架来作为JPA的基本实现,也会配置使用事务的方式。

注意:persistence.xml,文件的名称是固定的,放的文件夹也是固定的,他需要放在类路径下的META-INF 目录下。

<?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">
	<!-- 
		事务方式(transaction-type):
		    transaction-type  默认是JTA,JTA是可以使用分布式事务。 我们这里直接用本地事务就可以了 (transaction-type=RESOURCE_LOCAL)
		    
		    name  表示持久化单元的名字。   创建EntitymanagerFactory需要用到
	-->
	
	<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
		<!-- 
			配置使用什么 ORM 产品来作为 JPA 的实现 
			1. 实际上配置的是  javax.persistence.spi.PersistenceProvider 接口的实现类
			2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点. 
		-->
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		
		<!-- 添加持久化类 -->
		<class>com.xxx.jpademo.jpademo01.pojo.Customer</class>
		<class>com.xxx.jpademo.jpademo01.pojo.Order</class>
	
		<properties>
			<!-- 连接数据库的基本信息 -->
			<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
			<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa"/>
			<property name="javax.persistence.jdbc.user" value="root"/>
			<property name="javax.persistence.jdbc.password" value="root"/>
			
			<!-- 配置 JPA 实现产品的基本属性。   因为用hibernate作为JPA的实现,这里说白了就是配置 hibernate 的基本属性 -->
			<property name="hibernate.format_sql" value="true"/>
			<property name="hibernate.show_sql" value="true"/>
			<property name="hibernate.hbm2ddl.auto" value="update"/>
			
		</properties>
	</persistence-unit>
</persistence>

  1. 创建实体类, 使用 annotation 来描述实体类跟数据库表之间的映射关系。
// 多的一端的 Entity
package com.xxx.jpademo.jpademo01.pojo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Table(name="jpa_orders")
@Entity
public class Order {

	private Integer id;
	private String orderName;

	private Customer customer;

	@GeneratedValue(strategy=GenerationType.AUTO)//这些注解可以在getter方法上加。
	@Id
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name="order_name")
	public String getOrderName() {
		return orderName;
	}

	public void setOrderName(String orderName) {
		this.orderName = orderName;
	}

	//映射单向 n-1 的关联关系,一般情况下他会在数据库里自动创建外键约束
	//使用 @ManyToOne 来映射多对一的关联关系
	//使用 @JoinColumn 来映射外键. 
	//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
	@JoinColumn(name="customer_id")
	@ManyToOne(fetch=FetchType.LAZY) //默认情况下(什么属性都不写), 使用左外连接的方式来获取 n 的一端的对象和其关联的 1 的一端的对象。  改掉就不会,根据需求定
	public Customer getCustomer() {
		return customer;
	}

	public void setCustomer(Customer customer) {
		this.customer = customer;
	}
}


// 一的一端的 Entity
@Table(name="jpa_cutomers")
@Entity
public class Customer {
    private Integer id;
	private String lastName;
	private String email;
	private int age;
	
	private Date birth;
	private Date createTime;
    
    // 写上相应的映射注解,getter、setter方法
}

  1. 测试增删改查
package com.xxx.jpademo.jpademo01;

import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.xxx.jpademo.jpademo01.pojo.Customer;
import com.xxx.jpademo.jpademo01.pojo.Order;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	/**
	 * 映射单向 n-1 的关联关系     (在多的那一端写下面的相关代码)
	 * 
	 * 使用 @ManyToOne 来映射多对一的关联关系
	 *      fetch属性:修改默认的关联属性的加载策略。(默认是左连接。例如:可以使用  fetch=FetchType.LAZY 来设置为懒加载的策略)
	 * 使用 @JoinColumn 来映射外键. 
	 *      name属性:表示数据库表中的外键列的名字
	 *
	 * 可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
	 * 
	 */
	
	
	/**
	 * 保存多对一时, 建议先保存 1 的一端, 后保存 n 的一端, 这样不会多出额外的 UPDATE 语句.
	 */
	@Test
	public void testManyToOnePersist(){
		Customer customer = new Customer(null,"GG","gg@163.com",18,new Date(),new Date());
		
		Order order1 = new Order();
		order1.setOrderName("G-GG-1");
		
		Order order2 = new Order();
		order2.setOrderName("G-GG-2");
		
		//设置关联关系
		order1.setCustomer(customer);
		order2.setCustomer(customer);
		
		//执行保存操作    保存多对一时, 建议先保存 1 的一端, 后保存 n 的一端, 这样不会多出额外的 UPDATE 语句. 因为保存完一的那一端,然后多的那一端才能拿到ID
		entityManager.persist(customer);
		
		entityManager.persist(order1);
		entityManager.persist(order2);
		
	}
	
	
	//默认情况下, 使用左外连接的方式来获取 n 的一端的对象和其关联的 1 的一端的对象. 
	//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
	@Test
	public void testManyToOneFind(){
		Order order = entityManager.find(Order.class, 1);
		System.out.println(order.getOrderName());
		
		System.out.println(order.getCustomer().getLastName());
	}
	
	
	//不能直接删除 1 的一端, 因为有外键约束. 
	@Test
	public void testManyToOneRemove(){
		//Order order = entityManager.find(Order.class, 1); 删除多的这一端可以随便删除
		//entityManager.remove(order);
		
		//不能直接删除 1 的一端, 因为有外键约束. 
		Customer customer = entityManager.find(Customer.class, 204);
		entityManager.remove(customer);
	}
	
	/**
	 * 修改就是直接修改了
	 */
	@Test
	public void testManyToOneUpdate(){
		Order order = entityManager.find(Order.class, 2);
		order.getCustomer().setLastName("FFF");
	}
}

映射单向 n-1 的关联关系 在多的那一端写 @ManyToOne 和 @JoinColumn

使用 @ManyToOne 来映射多对一的关联关系
fetch属性:修改默认的关联属性的加载策略。

​ 这个注解可以什么属性都不写,在entityManager.find(Order.class, 1);查询的时候默认会用左连接的方式查询。可以使用 fetch=FetchType.LAZY 来设置为懒加载的策略

使用 @JoinColumn 来映射外键.
name属性:表示数据库表中的外键列的名字

在增删改查的时候有一些注意事项,具体的看 测试增删改查 代码中的相关注释。

4.2、单向一对多

操作步骤:

  1. 创建工程

可以用eclipse创建JPA工程,new–>找到 JPA Project创建就行;也可以创建普通的java工程,也可用maven创建普通的java工程

  1. 加入依赖

maven的方式和上面Hello Word的依赖引入是一样的

  1. 编写persistence.xml文件

persistence.xml这个实际上是JPA最基本的配置文件,这里面会包含连接数据库的基本信息,会包含使用哪个ORM框架来作为JPA的基本实现,也会配置使用事务的方式。

注意:persistence.xml,文件的名称是固定的,放的文件夹也是固定的,他需要放在类路径下的META-INF 目录下。

这个文件的内容和 上面个的单向多对一是一样的

  1. 创建实体类, 使用 annotation 来描述实体类跟数据库表之间的映射关系。
// 多的一端的 Entity

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

@Table(name="jpa_orders")
@Entity
public class Order {
    private Integer id;
	private String orderName;

	// 写上 getter、setter方法和相应的数据库字段映射的注解
}


//一的一端的 Entity

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

@Table(name="jpa_cutomers")
@Entity
public class Customer {
	
	private Integer id;
	private String lastName;
	private String email;
	private int age;
	
	private Date birth;
	private Date createTime;
	
	private Set<Order> orders = new HashSet<Order>();
	
	
	public Customer() {}
	
	public Customer(Integer id, String lastName, String email, int age,Date birth,Date createTime) {
		super();
		this.id = id;
		this.lastName = lastName;
		this.email = email;
		this.age = age;
		this.birth = birth;//年月日时分秒的时间格式,而且都是datetime类型
		this.createTime = createTime;
	}
	
	@GeneratedValue(strategy=GenerationType.AUTO)//这些注解可以在getter方法上加。
	@Id
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	@Column(name="last_name",length=50,nullable=false)
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Temporal(TemporalType.DATE)
	public Date getBirth() {
		return birth;
	}
	public void setBirth(Date birth) {
		this.birth = birth;
	}
	
	@Column(name="create_time")
	@Temporal(TemporalType.TIMESTAMP)
	public Date getCreateTime() {
		return createTime;
	}

	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}

	/**
	 * 映射单向 1-n 的关联关系,一般情况下他会在数据库里自动创建外键约束
	 * 使用 @OneToMany 来映射 1-n 的关联关系
	 * 使用 @JoinColumn 来映射外键列的名称
	 * 
	 * 可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略。(默认对关联的多的一方使用懒加载的加载策略。使用set集合时,才会发送sql) 
	 *     想要改掉这个默认的策略可以设置fetch属性。   
	 *     例如:@OneToMany(fetch = FetchType.EAGER),他就会变成迫切的左外连接了。(用左连接查询并初始化set集合)
	 * 
	 * 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略. 
	 *     (默认情况下, 若删除 1 的一端, 则会先把关联的 多 的一端的外键置空, 然后进行删除.  delete 和 update;
	 *     例如:@OneToMany(cascade = {CascadeType.REMOVE}) 他就会将1的一端删掉,同时也将多的一端也删掉  3个delete 语句(级联删除))
	 *     
	 */
	@JoinColumn(name="customer_id")
	@OneToMany(fetch = FetchType.EAGER,cascade = {CascadeType.REMOVE})
	public Set<Order> getOrders() {
		return orders;
	}

	public void setOrders(Set<Order> orders) {
		this.orders = orders;
	}
	
	//一个特殊的方法,不需要映射为数据库表的一列。加上@Transient
	@Transient
	public String getInfo() {
		return "不想让info作为数据库的一列就加上@Transient。因为不加会默认加上@Basic";
	}
	
	@Override
	public String toString() {
		return "Customer [id=" + id + ", lastName=" + lastName + ", email=" + email + ", age=" + age + "]";
	}
	
}

  1. 测试增删改查
package com.xxx.jpademo.jpademo01;

import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.xxx.jpademo.jpademo01.pojo.Customer;
import com.xxx.jpademo.jpademo01.pojo.Order;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	
	/**
	 * 映射单向 1-n 的关联关系  (在1的一端设置)
	 * 使用 @OneToMany 来映射 1-n 的关联关系
	 * 使用 @JoinColumn 来映射外键列的名称
	 * 
	 * 可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略。(默认对关联的多的一方使用懒加载的加载策略。使用set集合时,才会发送sql) 
	 *     想要改掉这个默认的策略可以设置fetch属性。   
	 *     例如:@OneToMany(fetch = FetchType.EAGER),他就会变成迫切的左外连接了。(用左连接查询并初始化set集合)
	 * 
	 * 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略. 
	 *     (默认情况下, 若删除 1 的一端, 则会先把关联的 多 的一端的外键置空, 然后进行删除.  delete 和 update;
	 *     例如:@OneToMany(cascade = {CascadeType.REMOVE}) 他就会将1的一端删掉,同时也将多的一端也删掉  3个delete 语句(级联删除))
	 *     
	 */
	
	
	
	@Test
	public void testOneToManyPersist(){
		Customer customer = new Customer(null,"单向1对多test","单向1对多@163.com",18,new Date(),new Date());
		
		Order order1 = new Order();
		order1.setOrderName("单向1对多-test-1");
		
		Order order2 = new Order();
		order2.setOrderName("单向1对多-test-2");
		
		//设置关联关系
		customer.getOrders().add(order1);
		customer.getOrders().add(order2);
		
		/**
		 * 在这里会出现3个insert和两个update
		 * 由1的一端维护关联关系,所以说你在保存order的时候,order不维护关联关系,那么order就不会放那个外键列的值。所以后面会额外的去发update语句
		 * 
		 * 就算换掉保存顺序也是一样的出现3个insert和两个update
		 * 
		 */
		entityManager.persist(customer);
		
		entityManager.persist(order1);
		entityManager.persist(order2);
	}
	
	@Test
	public void testOneToManyFind(){
		/**
		 * 默认对关联的多的一方使用懒加载的加载策略  使用set集合时,才会发送sql
		 * 
		 * 例如:可以在@OneToMany注解中加上  fetch = FetchType.EAGER  他就会变成迫切的左外连接了。(用左连接查询并初始化set集合)
		 * 
		 */
		Customer customer = entityManager.find(Customer.class, 205);
		
		System.out.println(customer);
		
		System.out.println(customer.getOrders().size());
	}
	
	
	@Test
	public void testOneToManyRemove(){
		/**
		 * 默认情况下, 若删除 1 的一端, 则会先把关联的 多 的一端的外键置空, 然后进行删除.  update 和 delete
		 * 
		 * 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略. 
		 * 
		 * 例如:可以在@OneToMany注解中加上  cascade = {CascadeType.REMOVE}  他就会将1的一端删掉,同时也将多的一端也删掉  3个delete 语句(级联删除)
		 */
		Customer customer = entityManager.find(Customer.class, 205);
		
		entityManager.remove(customer);
		
	}
	
	/**
	 * 修改就是直接修改了
	 */
	@Test
	public void testOneToManyUpdate(){
		Customer customer = entityManager.find(Customer.class, 205);
		
		customer.getOrders().iterator().next().setOrderName("单向1对多-test-2");
	}
}

4.3、双向一对多

操作步骤:

  1. 创建工程

可以用eclipse创建JPA工程,new–>找到 JPA Project创建就行;也可以创建普通的java工程,也可用maven创建普通的java工程

  1. 加入依赖

maven的方式和上面Hello Word的依赖引入是一样的

  1. 编写persistence.xml文件

persistence.xml这个实际上是JPA最基本的配置文件,这里面会包含连接数据库的基本信息,会包含使用哪个ORM框架来作为JPA的基本实现,也会配置使用事务的方式。

注意:persistence.xml,文件的名称是固定的,放的文件夹也是固定的,他需要放在类路径下的META-INF 目录下。

这个文件的内容和 上面个的单向多对一是一样的

  1. 创建实体类, 使用 annotation 来描述实体类跟数据库表之间的映射关系。
// 一的一端
import javax.persistence.*;

@Table(name="jpa_cutomers")
@Entity
public class Customer {
    private Integer id;
	private String lastName;
	private String email;
	private int age;
	
	private Date birth;
	private Date createTime;
	
	private Set<Order> orders = new HashSet<Order>();
    
    
    /**
	 * 双向1对多 和 双向多对1  是一回事。从左看就是多对一,从右看就算一对多
	 * 
	 * 需要注意的是两边的外键列要一致   @JoinColumn(name="customer_id") 要一致
	 * 
	 * 映射双向 1-n 的关联关系
	 * 1、在1的一端使用 @OneToMany 来映射 1-n 的关联关系;在多的一端使用 @ManyToOne 来映射多对一的关联关系
	 * 
	 * 2、使用 @JoinColumn 来映射外键列的名称。要与多的一端的 @JoinColumn 一致
	 * 
	 * 
	 * 在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系, 这样会有效的减少 SQL 语句. 
	 * 
	 * 通常在1的一端放弃维护关联关系,在 @OneToMany 中使用 mappedBy 属性,
	 *     例如@OneToMany(mappedBy="customer"),属性值写Order类中的customer属性名
	 * 
	 * 
	 * 注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了. 
	 * 
	 * 
	 * 可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略。(默认对关联的多的一方使用懒加载的加载策略。使用set集合时,才会发送sql) 
	 *     想要改掉这个默认的策略可以设置fetch属性。   
	 *     例如:@OneToMany(fetch = FetchType.EAGER),他就会变成迫切的左外连接了。(用左连接查询并初始化set集合)
	 * 
	 * 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略. 
	 *     (默认情况下, 若删除 1 的一端, 则会先把关联的 多 的一端的外键置空, 然后进行删除.  delete 和 update;
	 *     例如:@OneToMany(cascade = {CascadeType.REMOVE}) 他就会将1的一端删掉,同时也将多的一端也删掉  3个delete 语句(级联删除))
	 *     
	 */
	//@JoinColumn(name="customer_id")
	@OneToMany(fetch = FetchType.EAGER,cascade = {CascadeType.REMOVE},mappedBy="customer")
	public Set<Order> getOrders() {
		return orders;
	}
    
    // ..... 省略一些setter、getter 和 映射注解的代码 和 构造器
    
}


// 多的一端
import javax.persistence.*;

@Table(name="jpa_orders")
@Entity
public class Order {
    private Integer id;
	private String orderName;
	
	private Customer customer;
    
    /**
	 * 
	 * 双向1对多 和 双向多对1  是一回事。从左看就是多对一,从右看就算一对多
	 * 
	 * 需要注意的是两边的外键列要一致   @JoinColumn(name="customer_id") 要一致
	 * 
	 * 
	 * 在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系, 这样会有效的减少 SQL 语句. 
	 * 
	 * 通常在1的一端放弃维护关联关系,在 @OneToMany 中使用 mappedBy 属性,
	 *     例如@OneToMany(mappedBy="customer"),属性值写Order类中的customer属性名
	 * 
	 * 
	 * 注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了. 
	 * 
	 * 
	 * 映射双向 1-n 的关联关系
	 * 1、在多的一端使用 @ManyToOne 来映射多对一的关联关系,在1的一端使用 @OneToMany 来映射 1-n 的关联关系
	 * 
	 * 2、使用 @JoinColumn 来映射外键. 要与一的一端的 @JoinColumn 一致
	 *    可以使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
	 */
	@JoinColumn(name="customer_id")
	@ManyToOne(fetch=FetchType.LAZY) //默认情况下, 使用左外连接的方式来获取 n 的一端的对象和其关联的 1 的一端的对象.  改掉就不会,根据需求定
	public Customer getCustomer() {
		return customer;
	}
    // ..... 省略一些setter、getter 和 映射注解的代码 和 构造器
}

  1. 测试增删改查
package com.xxx.jpademo.jpademo01;

import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.xxx.jpademo.jpademo01.pojo.Customer;
import com.xxx.jpademo.jpademo01.pojo.Order;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	
	@Test
	public void testPersist_two_way_1_n(){
		Customer customer = new Customer(null,"双向1对多test2","双向1对多@163.com",18,new Date(),new Date());
		
		Order order1 = new Order();
		order1.setOrderName("双向1对多-test-2");
		
		Order order2 = new Order();
		order2.setOrderName("双向1对多-test-2");
		
		//设置关联关系
		customer.getOrders().add(order1);
		customer.getOrders().add(order2);
		
		//设置关联关系 这块故意搞复杂一点;我们还可以
		order1.setCustomer(customer);
		order2.setCustomer(customer);
		
		
		/**
		 * 若是双向 1-n 的关联关系, 执行保存时
		 * 若先保存 n 的一端, 再保存 1 的一端, 默认情况下, 会多出 n 条 UPDATE 语句.
		 * 
		 *     因为这时候两边都维护关联关系,就是说你保存的是order,他尝试的往customer_id,但遗憾的是这时候还没有customer_id就先放入null,
		 *     然后在插入 customer 这时候就会给order的customer_id补上值,就会多出update语句了;
		 *     然后在 customer 也维护关联关系,这时候也会发送update语句
		 *     
		 *     在这个demo里面会有3个insert和4个update   跟hibernate一样
		 * 
		 * 
		 * 若先保存 1 的一端, 则会多出 n 条 UPDATE 语句
		 *     customer先插入,在插入order,这时候order关联的customer_id就有了,所以说order在插入记录的同时那个外键维护关系就完成了;
		 *     但是customer要在维护一遍这时候就多2条update
		 *     
		 *     在这个demo里面会有3个insert和2个update   跟hibernate一样
		 *     
		 *     
		 * 
		 * 在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系, 这样会有效的减少 SQL 语句. 
		 * 
		 * 通常在1的一端放弃维护关联关系,在 @OneToMany 中使用 mappedBy 属性,
		 *     例如@OneToMany(mappedBy="customer"),属性值写Order类中的customer属性名
		 * 
		 * 
		 * 注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了. 
		 * 
		 */
		entityManager.persist(customer);
		
		entityManager.persist(order1);
		entityManager.persist(order2);
		
	}
	
	
	@Test
	public void testFind_two_way_1_nV1(){
		/**
		 * 默认对关联的多的一方使用懒加载的加载策略  使用set集合时,才会发送sql
		 * 
		 * 例如:可以在@OneToMany注解中加上  fetch = FetchType.EAGER  他就会变成迫切的左外连接了。(用左连接查询并初始化set集合)
		 * 
		 */
		Customer customer = entityManager.find(Customer.class, 207);
		
		System.out.println(customer);
		
		System.out.println(customer.getOrders().size());
	}
	
	@Test
	public void testFind_two_way_1_nV2(){
		/**
		 * 在双向1-n中直接查询多的一方,还是在Order中的Customer属性用上layz吧。不然查询左连接查一遍,还会在单独查一遍Order
		 * 
		 * 或者两边都不设置 fetch 属性。默认就是用多的一方默认的一个左连接
		 * 
		 */
		Order order = entityManager.find(Order.class, 8);
		
		System.out.println(order);
		
		System.out.println(order.getCustomer());
	}
	
	@Test
	public void testOneToManyRemove(){
		/**
		 * 默认情况下, 若删除 1 的一端, 则会先把关联的 多 的一端的外键置空, 然后进行删除.  delete 和 update
		 * 
		 * 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略. 
		 * 
		 * 例如:可以在@OneToMany注解中加上  cascade = {CascadeType.REMOVE}  他就会将1的一端删掉,同时也将多的一端也删掉  3个delete 语句(级联删除)
		 */
		//删除没有测试。一般来说最好还是 先删除多的一方再删除1的一方。。。就算先断掉关联关系
		//Customer customer = entityManager.find(Customer.class, 205);
		
		//entityManager.remove(customer);
		
	}
	
	/**
	 * 修改没有测试。。但是一般来说:
	 * 修改就是直接修改了
	 */
	@Test
	public void testOneToManyUpdate(){
		//Customer customer = entityManager.find(Customer.class, 205);
		
		//customer.getOrders().iterator().next().setOrderName("单向1对多-test-2");
	}
}

双向1对多 和 双向多对1 是一回事。从左看就是多对一,从右看就是一对多。

需要注意的是两边的外键列要一致 @JoinColumn(name=“customer_id”) 要一致

映射双向 1-n 的关联关系
1、在多的一端使用 @ManyToOne 来映射多对一的关联关系,在1的一端使用 @OneToMany 来映射 1-n 的关联关系

2、使用 @JoinColumn 来映射外键. 要与一的一端的 @JoinColumn 一致

通常在1的一端放弃维护关联关系,在 @OneToMany 中使用 mappedBy 属性,例如:在Customer类中写 @OneToMany(mappedBy="customer"),mappedBy的属性值写Order类中的customer属性名

注意

若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了。还有一些注解的属性使用,可以看上面的实体代码中的注释说明。

4.4、双向一对一

操作步骤:

  1. 创建工程

可以用eclipse创建JPA工程,new–>找到 JPA Project创建就行;也可以创建普通的java工程,也可用maven创建普通的java工程

  1. 加入依赖

maven的方式和上面Hello Word的依赖引入是一样的

  1. 编写persistence.xml文件

persistence.xml这个实际上是JPA最基本的配置文件,这里面会包含连接数据库的基本信息,会包含使用哪个ORM框架来作为JPA的基本实现,也会配置使用事务的方式。

注意:persistence.xml,文件的名称是固定的,放的文件夹也是固定的,他需要放在类路径下的META-INF 目录下。

<?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">
	<!-- 
		事务方式(transaction-type):
		    transaction-type  默认是JTA,JTA是可以使用分布式事务。 我们这里直接用本地事务就可以了 (transaction-type=RESOURCE_LOCAL)
		    
		    name  表示持久化单元的名字。   创建EntitymanagerFactory需要用到
	-->
	
	<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
		<!-- 
			配置使用什么 ORM 产品来作为 JPA 的实现 
			1. 实际上配置的是  javax.persistence.spi.PersistenceProvider 接口的实现类
			2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点. 
		-->
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		
		<!-- 添加持久化类 -->
		<class>com.xxx.jpademo.jpademo01.pojo.Department</class>
		<class>com.xxx.jpademo.jpademo01.pojo.Manager</class>
		
		<properties>
			<!-- 连接数据库的基本信息 -->
			<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
			<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa"/>
			<property name="javax.persistence.jdbc.user" value="root"/>
			<property name="javax.persistence.jdbc.password" value="root"/>
			
			<!-- 配置 JPA 实现产品的基本属性。   因为用hibernate作为JPA的实现,这里说白了就是配置 hibernate 的基本属性 -->
			<property name="hibernate.format_sql" value="true"/>
			<property name="hibernate.show_sql" value="true"/>
			<property name="hibernate.hbm2ddl.auto" value="update"/>
			
		</properties>
	</persistence-unit>
</persistence>

  1. 创建实体类, 使用 annotation 来描述实体类跟数据库表之间的映射关系。
package com.xxx.jpademo.jpademo01.pojo;

import javax.persistence.*;

/**
 * 这里的业务关系是一个部门对应一个经理, 一个经理对应一个部门
 * 
 * 基于外键的双向1-1关系
 * 
 * 这个外键放在那张表里面? 实际上双向关联关系放哪边都可以 , 双向1-1他两是完全平等的。这里我们选择放在Department里面!
 * (jpa_departments的外键指向manager的主键)
 * 
 * 
 * 操作方法:
 *     1、在两边的类都写上对方的类作为属性; private Department dept;  private Manager mgr;双方都要对应写入
 *     
 *     2、都用 @OneToOne 来映射1-1关系
 *     
 *     3、在随意一方使用 @JoinColumn 来映射外键, 映射外键的话就理所当然的成为了维护关系的一方。但是映射1-1的话还是不要让他重复,设置上unique=true
 *     
 *     4、对于不维护关联关系, 没有外键的一方, 使用 @OneToOne 来进行映射, 设置 mappedBy=对方关联关系的属性名。 就是不要让双方都维护关联关系
 * 
 */
@Table(name="jpa_departments")
@Entity
public class Department {
	
	private Integer id;
	private String deptName;
	
	private Manager mgr;

	//不写属性默认的就是    @GeneratedValue(strategy=GenerationType.AUTO)
	@GeneratedValue
	@Id
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name="dept_name")
	public String getDeptName() {
		return deptName;
	}
	public void setDeptName(String deptName) {
		this.deptName = deptName;
	}
	/**
	  * 这个外键放在那张表里面? 实际上双向关联关系放哪边都可以 , 双向1-1他两是完全平等的。这里我们选择放在Department里面!
	  * (jpa_departments的外键指向manager的主键)
	  * 
	  * 使用@OneToOne来映射1-1关系
	  * 若需要在当前数据表中添加主键则需要使用 @JoinColumn 来进行映射. 注意, 1-1 关联关系, 所以需要添加 unique=true
	  * 
	  * 在双向1-1的关系中 使用 @JoinColumn 来映射外键, 映射外键的话就理所当然的成为了维护关系的一方。但是映射1-1的话还是不要让他重复,设置上unique=true
	  * 
	  * 默认情况下(不设fetch的情况下), 若用find(Department.class, 1);
	  *    获取维护关联关系的一方(Department), 则会通过左外连接获取其关联的对象.(两个左连接语句)
	  *    
	  * 可以在维护关联关系的一方设置 @OneToOne(fetch=FetchType.LAZY) 让他变为懒加载。当用到关联属性(Manager mgr)的时候在发送sql
	  * 
	 */
	@JoinColumn(name="mgr_id", unique=true)
	@OneToOne(fetch=FetchType.LAZY)
	public Manager getMgr() {
		return mgr;
	}

	public void setMgr(Manager mgr) {
		this.mgr = mgr;
	}

	@Override
	public String toString() {
		return "Department [id=" + id + ", deptName=" + deptName + "]";
	}
}



package com.xxx.jpademo.jpademo01.pojo;

import javax.persistence.*;

@Table(name="jpa_managers")
@Entity
public class Manager {

	private Integer id;
	private String mgrName;
	
	private Department dept;

	//不写属性默认的就是    @GeneratedValue(strategy=GenerationType.AUTO)
	@GeneratedValue
	@Id
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name="mgr_name")
	public String getMgrName() {
		return mgrName;
	}
	public void setMgrName(String mgrName) {
		this.mgrName = mgrName;
	}
	
	//对于不维护关联关系, 没有外键的一方, 使用 @OneToOne 来进行映射, 建议设置 mappedBy=对方关联关系的属性名
	@OneToOne(mappedBy="mgr")
	public Department getDept() {
		return dept;
	}
	public void setDept(Department dept) {
		this.dept = dept;
	}

	@Override
	public String toString() {
		return "Manager [id=" + id + ", mgrName=" + mgrName + "]";
	}
}

  1. 测试增删改查
package com.xxx.jpademo.jpademo01;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.xxx.jpademo.jpademo01.pojo.Department;
import com.xxx.jpademo.jpademo01.pojo.Manager;


public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	@Test
	public void testPersist_two_way_1_1(){
		Manager mgr = new Manager();
		mgr.setMgrName("双向1-1管理者名字");
		
		Department dept = new Department();
		dept.setDeptName("双向1-1部门名字");
		
		//设置关联关系
		mgr.setDept(dept);
		dept.setMgr(mgr);
		
		/**
		 * 双向 1-1 的关联关系, 建议先保存不维护关联关系的一方, 即没有外键的一方, 这样不会多出 UPDATE 语句.
		 * 如果先保存维护关系的一方 Department 就会出现没有外键ID的情况,后面就会发送update语句进行补上
		 * 
		 * 执行保存操作
		 */
		entityManager.persist(mgr);
		entityManager.persist(dept);
	}
	
	@Test
	public void testFind_two_way_1_1V1(){
		/**
		 * 1.默认情况下(Department不设fetch的情况下), 若获取维护关联关系的一方(Department), 则会通过左外连接获取其关联的对象. (两个左连接语句)
		 * 
		 * 但可以通过 @OntToOne 的 fetch 属性来修改加载策略.
		 *     例如:可以在维护关联关系的一方(Department)设置 @OneToOne(fetch=FetchType.LAZY) 让他变为懒加载。当用到关联属性的时候在发送sql
		 */
		Department dept = entityManager.find(Department.class, 1);
		System.out.println(dept.getDeptName());
		
		//当在维护关联关系的一方(Department)设置 @OneToOne(fetch=FetchType.LAZY) ,就是一个代理对象
		System.out.println(dept.getMgr().getClass().getName());
		
	}
	
	@Test
	public void testFind_two_way_1_1V2(){
		/**
		 * 默认情况下(Manager不设fetch的情况下,Department设不设没关系), 若获取不维护关联关系的一方(Manager), 则也会通过左外连接获取其关联的对象. 
		 * 
		 * 可以通过 @OneToOne 的 fetch 属性来修改加载策略. 但依然会再发送 SQL 语句(发送两条)来初始化其关联的对象
		 * 
		 * 这说明在不维护关联关系的一方, 不建议修改 fetch 属性. (改了发两条sql,不改就发1条sql),而且用没用到Department都会发两条sql
		 * 
		 * 所以不建议修改 不维护关联关系的一方 fetch 属性
		 * 
		 * 在不维护关联关系的一方(Manager)改了 fetch 属性,为什么一定要另外发 sql 去查询关联对象呢?
		 * 而维护关联关系的一方(Department)改了 fetch = FetchType.LAZY 就只会发送一条 sql
		 * 因为在维护关系的一方你设置了外键,当你将 fetch 改为 FetchType.LAZY,他就能将关联的对象弄成一个代理 或者 这个外键没有数据给你弄一个空。
		 * 而在不维护关联关系的一方,没有外键,程序不知道有当前对象(记录)没有相应的关联对象,它没有办法搞一个代理或者空,只能是多发送一条sql
		 */
		
		Manager mgr = entityManager.find(Manager.class, 1);
		System.out.println(mgr.getMgrName());
		
		System.out.println(mgr.getDept().getClass().getName());
	}
	// 这里介绍的是基于外键的一对一关联关系;基于主键的一对一可以用@PrimaryKeyJoinColumn注解,这里就不讲了
}

4.5、双向多对多

操作步骤:

  1. 创建工程

可以用eclipse创建JPA工程,new–>找到 JPA Project创建就行;也可以创建普通的java工程,也可用maven创建普通的java工程

  1. 加入依赖

maven的方式和上面Hello Word的依赖引入是一样的

  1. 编写persistence.xml文件

persistence.xml这个实际上是JPA最基本的配置文件,这里面会包含连接数据库的基本信息,会包含使用哪个ORM框架来作为JPA的基本实现,也会配置使用事务的方式。

注意:persistence.xml,文件的名称是固定的,放的文件夹也是固定的,他需要放在类路径下的META-INF 目录下。

<?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">
	<!-- 
		事务方式(transaction-type):
		    transaction-type  默认是JTA,JTA是可以使用分布式事务。 我们这里直接用本地事务就可以了 (transaction-type=RESOURCE_LOCAL)
		    
		    name  表示持久化单元的名字。   创建EntitymanagerFactory需要用到
	-->
	
	<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
		<!-- 
			配置使用什么 ORM 产品来作为 JPA 的实现 
			1. 实际上配置的是  javax.persistence.spi.PersistenceProvider 接口的实现类
			2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点. 
		-->
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		
		<!-- 添加持久化类 -->
		<class>com.xxx.jpademo.jpademo01.pojo.Category</class>
		<class>com.xxx.jpademo.jpademo01.pojo.Item</class>
		
		<properties>
			<!-- 连接数据库的基本信息 -->
			<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
			<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa"/>
			<property name="javax.persistence.jdbc.user" value="root"/>
			<property name="javax.persistence.jdbc.password" value="root"/>
			
			<!-- 配置 JPA 实现产品的基本属性。   因为用hibernate作为JPA的实现,这里说白了就是配置 hibernate 的基本属性 -->
			<property name="hibernate.format_sql" value="true"/>
			<property name="hibernate.show_sql" value="true"/>
			<property name="hibernate.hbm2ddl.auto" value="update"/>
			
		</properties>
	</persistence-unit>
</persistence>

  1. 创建实体类, 使用 annotation 来描述实体类跟数据库表之间的映射关系。
package com.xxx.jpademo.jpademo01.pojo;


import java.util.HashSet;
import java.util.Set;

import javax.persistence.*;

@Table(name="jpa_categories")
@Entity
public class Category {

	/**
	 * 双向多对多关系
	 * 使用 @ManyToMany 注解。同时需要使用中间表 使用@JoinTable注解;双向多对多必须要一方放弃维护关联关系,不然会在中间表中会有主键重复的问题。和hibernate一样
	 * 
	 * 这个例子中:
	 *     一个类别可以有多个商品,同样一个商品可以属于多个类别。(Item是商品,Category是类别)
	 * 
	 * 操作:
	 *     1、双方使用 @ManyToMany 注解来映射多对多关联关系
	 *     
	 *     2、选择一方放弃维护关联关系。 不然会在中间表中会有主键重复的问题。
	 *        例如:在Category类中写 @ManyToMany(mappedBy="categories"),属性值写Item类中的Category属性名
	 *        
	 *     3、在维护关系的一方写上@JoinTable。  怎么配置可以查看Item类
	 */
	
	private Integer id;
	private String categoryName;
	
	private Set<Item> items = new HashSet<>();
	
	public Category() {}

	public Category(Integer id, String categoryName) {
		super();
		this.id = id;
		this.categoryName = categoryName;
	}

	@GeneratedValue
	@Id
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name="category_name")
	public String getCategoryName() {
		return categoryName;
	}

	public void setCategoryName(String categoryName) {
		this.categoryName = categoryName;
	}
	/**
	 * 选择一方放弃维护关联关系。 不然会在中间表中会有主键重复的问题
	 *     例如:在Category类中写 @ManyToMany(mappedBy="categories"),属性值写Item类中的Category属性名
	 * 
	 */
	@ManyToMany(mappedBy="categories")
	public Set<Item> getItems() {
		return items;
	}

	public void setItems(Set<Item> items) {
		this.items = items;
	}
}



package com.xxx.jpademo.jpademo01.pojo;


import java.util.HashSet;
import java.util.Set;

import javax.persistence.*;

@Table(name="jpa_items")
@Entity
public class Item {

	private Integer id;
	private String itemName;
	
	private Set<Category> categories = new HashSet<>();
	
	public Item() {}

	public Item(Integer id, String itemName) {
		super();
		this.id = id;
		this.itemName = itemName;
	}

	@GeneratedValue
	@Id
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name="item_name")
	public String getItemName() {
		return itemName;
	}

	public void setItemName(String itemName) {
		this.itemName = itemName;
	}

	/**
	 * 使用 @ManyToMany 注解来映射多对多关联关系
	 * 使用 @JoinTable 来映射中间表
	 * 1. name 指向中间表的名字
	 * 
	 * 2. joinColumns 映射当前类所在的表在中间表中的外键
	 *    2.1 name 指定外键列的列名
	 *    2.2 referencedColumnName 指定外键列关联当前表的哪一列
	 *    
	 * 3. inverseJoinColumns 映射关联的类所在中间表的外键
	 */
	@JoinTable(name="jpa_item_category",//中间表名称
			joinColumns={@JoinColumn(name="item_id", referencedColumnName="id")},//当前这个class(类)在中间表里的外键名叫item_id,指向于当前类的id属性
			//关联类(Category)在中间表的外键叫category_id,指向于Category类中的id
			inverseJoinColumns={@JoinColumn(name="category_id", referencedColumnName="id")})
	@ManyToMany
	public Set<Category> getCategories() {
		return categories;
	}

	public void setCategories(Set<Category> categories) {
		this.categories = categories;
	}
}

  1. 测试增删改查
package com.xxx.jpademo.jpademo01;

import javax.persistence.*;
import org.junit.*;
import com.xxx.jpademo.jpademo01.pojo.Category;
import com.xxx.jpademo.jpademo01.pojo.Item;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	//多对所的保存
	@Test
	public void testPersist_two_way_n_n(){

		Item i1 = new Item(null,"商品-1");
	
		Item i2 = new Item(null,"商品-2");
		
		Category c1 = new Category(null,"类别-1");
		
		Category c2 = new Category(null,"类别-2");
		
		//设置关联关系
		i1.getCategories().add(c1);//商品1属于类别1,类别2
		i1.getCategories().add(c2);//商品1属于类别1,类别2
		
		i2.getCategories().add(c1);//商品2属于类别1,类别2
		i2.getCategories().add(c2);//商品2属于类别1,类别2
		
		c1.getItems().add(i1);//类别1 里 有商品1
		c1.getItems().add(i2);//类别1 里 有商品2
		
		c2.getItems().add(i1);//类别2 里 有商品1
		c2.getItems().add(i2);//类别2 里 有商品2
		
		//执行保存
		entityManager.persist(i1);
		entityManager.persist(i2);
		entityManager.persist(c1);
		entityManager.persist(c2);
	}
	
	@Test
	public void testFind_two_way_n_nV1(){
		/**
		 * 查询维护关联关系的一方   默认使用懒加载的策略
		 * 
		 * 使用维护关联关系的一方获取, 还是使用不维护关联关系的一方获取, SQL 语句相同. 
		 */
		Item item = entityManager.find(Item.class, 2);
		System.out.println(item.getItemName());
		
		System.out.println(item.getCategories().size());//inner join 查询
		
		/**
		 * 查询不维护关联关系的一方   默认使用懒加载的策略
		 * 
		 * 使用维护关联关系的一方获取, 还是使用不维护关联关系的一方获取, SQL 语句相同. 
		 */
		//Category category = entityManager.find(Category.class, 1);
		//System.out.println(category.getCategoryName());
		//System.out.println(category.getItems().size());//inner join 查询
	}
	
	@Test
	public void testFind_two_way_n_nV2(){
		
	}
}

5、查询操作

5.1、OID 检索方式查询

按照对象的 OID 来检索对象。主要指的是entityManager.find(...)entityManager.getReference (...)方法

5.2、导航对象图检索方式

这个查询方式跟数据关联有关。其实也是使用entityManager.find(...)entityManager.getReference (...)方法查询到对象之后通过 getxxx() 方法获取到关联对象的一种方式,在 JPA 中使用find(...)getReference (...)查询出来的持久化状态的对象,直接通过 getxxx() 方法去获取关联的对象会自己发送SQL去查询关联的对象,配置了迫切左外连接查询方式除外,迫切左外连接其实在使用find(...)getReference (...)查询的时候就直接将关联的对象给查询出来了,通过 getxxx() 方法也是能获取到对应的数据的。

5.3、JPQL检索方式查询

JPQL语言,即 Java Persistence Query Language 的简称。JPQL 是一种和 SQL 非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL 查询,从而屏蔽不同数据库的差异。

JPQL语言的语句可以是 select 语句、update 语句或delete语句,它们都通过 Query 接口封装执行

JPQL 语句的写法可以看作语句中数据库表名变成了类名,字段名变成了属性名。语句和 SQL 很像,但仔细看它就是查询对象的语句。

其语法可表示为:

[select_clause] 

form_clause 

[where_clause] 

[groupby_clause] 

[having_clause]

[orderby_clause]

中括号部分是可选的。也就是说可以不写,包括select也是可以不写的。
  • select 是JPQL的一个关键字,用来指定查询返回的结果实体或实体的某些属性。

    • 例如: SELECT o FROM Order o 或 SELECT o FROM Order as o 关键字 as 可以省略。
  • from 是JPQL的一个关键字,from 子句声明查询源实体类,并指定标识符变量(相当于SQL表的别名)。from 子句是查询语句的必选子句

    • 例如:FROM Customer 或者 FROM Customer WHERE xxx = xxx 当只是查询一个实体的全部字段、属性的情况下 SELECT 关键字是可以不用写的。
    • 如果不希望返回重复实体,可使用关键字 distinct 修饰。例如:SELECT DISTINCT o FROM Order o去重复需要加上SELECT 和 DISTINCT 关键字。
    • 关键字通常全大写或全小写,不建议大小写混用
  • where 是JPQL的一个关键字,where子句用于指定查询条件,where后面跟的是条件表达式。where条件表达式中可用的运算符基本上与SQL一致,包括:

    • 算术运算符:+ - * / +(正) -(负)

    • 关系运算符:== <> > >= < <= between…and like in is null 等

    • 逻辑运算符: and or  not

    • 下面是一些常见查询表达式示例:

      select o from Orders o where o.id = 1
      
      select o from Orders o where o.id > 3 and o.confirm = 'true' 
      
      select o from Orders o where o.address.streetNumber >= 123
      
      -- 以下语句查询 Id 介于 100 至 200 之间的订单。
      select o from Orders o where o.id between 100 and 200
      
      -- 以下语句查询国籍为的 'US'、'CN'或'JP' 的客户。
      select c from Customers c where c.county in ('US','CN','JP')
      
      -- 以下语句查询手机号以139开头的客户。%表示任意多个字符序列,包括0个。
      select c from Customers c where c.phone like '139%'
      
      -- 以下语句查询名字包含4个字符,且234位为ose的客户。_表示任意单个字符。
      select c from Customers c where c.lname like '_ose' 
      
      -- 以下语句查询电话号码未知的客户。Nul l用于测试单值是否为空。
      select c from Customers c where c.phone is null
      
      -- 以下语句查询尚未输入订单项的订单。empty用于测试集合是否为空。
      select o from Orders o where o.orderItems is empty
      
  • order by是JPQL的一个关键字,order by子句用于对查询结果集进行排序。和SQL的用法类似,可以用 “asc“ 和 "desc“ 指定升降序。如果不显式注明,默认为升序。

    select o from Orders o order by o.id
    
    select o from Orders o order by o.address.streetNumber desc
    
    select o from Orders o order by o.customer asc, o.id desc
    
  • group by是JPQL的一个关键字, group by子句用于对查询结果分组统计,通常需要使用聚合函数。常用的聚合函数主要有 AVG、SUM、COUNT、MAX、MIN 等,它们的含义与SQL相同。

  • having 是JPQL的一个关键字,having子句用于对 group by 分组设置约束条件,用法与where 子句基本相同,不同是 where 子句作用于基表或视图,以便从中选择满足条件的记录;having 子句则作用于分组,用于选择满足条件的组,其条件表达式中通常会使用聚合函数。

    • 例如,这个语句用于查询订购总数大于100的商家所售商品及数量:select o.seller, o.goodId, sum(o.amount) from V_Orders o group by o.seller, o.goodId having sum(o.amount) > 100
5.3.1、JPQL绑定参数执行查询

JPQL绑定参数可以使用占位符方式参数绑定或者命名参数方式参数绑定。

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	/**
     * 1、占位符方式参数绑定。
     */
	@Test
	public void testJPQLQuery_v1(){
        // 其中 ?1 代表第一个参数,?2 代表第二个参数。在执行查询之前需要使用重载方法Query.setParameter(position, value) 提供参数值
	    String jpql = "FROM Customer c WHERE c.age > ?1 AND c.lastName LIKE ?2";
	    Query query = entityManager.createQuery(jpql);

	    //占位符的索引是从 1 开始
	    query.setParameter(1, 1);
        query.setParameter(2, "%TEST%");
        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
	    List<Customer> customers = query.getResultList();
	    System.out.println(customers.size());
	}
    /**
     * 2、命名参数方式参数绑定。
     */
    @Test
	public void testJPQLQuery_v2(){
        // 在 JPQL 查询语句中定义命名参数, 命名参数以 “:” 开头。执行查询前须使用Query.setParameter(name, value)方法给参数赋值
	    String jpql = "FROM Customer c WHERE c.age > :ageParam AND c.lastName LIKE :lastNameParam";
	    Query query = entityManager.createQuery(jpql);

	    query.setParameter("ageParam", 1);// 给命名参数赋值
	    query.setParameter("lastNameParam", "%TEST%");
        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
	    List<Customer> customers = query.getResultList();
	    System.out.println(customers.size());
	}
}
5.3.2、JPQL查询单个实体对象或者集合
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    
	@Test
	public void testJPQLQueryEntity(){
        // 其中 ?1 代表第一个参数,在执行查询之前需要使用重载方法Query.setParameter(position, value) 提供参数值
	    String jpql = "FROM Customer c WHERE c.id = ?1";
	    Query query = entityManager.createQuery(jpql);

	    //占位符的索引是从 1 开始
	    query.setParameter(1, 1);
        // 可以通过getSingleResult()方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
	    Customer customer = (Customer)query.getSingleResult();
	    System.out.println(customer);
	}
    
    @Test
	public void testJPQLQueryList(){
        // 其中 ?1 代表第一个参数,在执行查询之前需要使用重载方法Query.setParameter(position, value) 提供参数值
	    String jpql = "FROM Customer c WHERE c.lastName like ?1";
	    Query query = entityManager.createQuery(jpql);

	    //占位符的索引是从 1 开始
	    query.setParameter(1, "%TEST%");
        // 可以通过getResultList()方法获取集合类型的结果,也就是获取多个数据。
	    List<Customer> list = (List<Customer>)query.getResultList();
	    System.out.println(list);
	}
}
5.3.3、JPQL查询单个属性(某个列)
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    
	@Test
	public void testJPQLQuerySingleAttr(){
        // 其中 ?1 代表第一个参数,在执行查询之前需要使用重载方法Query.setParameter(position, value) 提供参数值
	    String jpql = "SELECT lastName FROM Customer c WHERE c.id = ?1";
	    Query query = entityManager.createQuery(jpql);

	    //占位符的索引是从 1 开始
	    query.setParameter(1, 1);
        // 可以通过getSingleResult()方法获取单个结果。 如果是返回多个就要使用(List<String>)query.getResultList();
	    String result = (String)query.getSingleResult();//getSingleResult()如果数据库没有对应的记录,将会报异常NoResultException
	    System.out.println(result);
	}
}
5.3.4、投影查询(JPQL查询部分属性)

投影查询:查询结果仅包含实体的部分属性(就是查询部分指定的属性)。需要通过 SELECT 关键字实现。

如果只须查询实体的部分属性而不需要返回整个实体。例如:

select o.id, o.customerName, o.address.streetNumber from Order o order by o.id

执行该查询返回的不再是 Orders 实体集合,而是一个对象数组的集合(Object[]),每一条记录都是返回 id 和 customerName 两列;这两个构成一个数组,多条记录就是多条数组,所以是数组的List

Object[]数组的集合对数据操作不是那么的友好,目前有3个方式来处理这个问题

  • new 一个类的构造器方式,new 的类是不是实体类都行,有可能不行(报错)就要写全类名
  • new map的方式。让JPA创建一个Map
  • 默认值的方式,就是在 JPQL 语句中用 null值 或者 0 或者 空字符串 之类的来赋给那些不需要查询的字段

代码示例:

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	/**
	 * 默认情况下, 若只查询部分属性, 则将返回 Object[] 类型的结果. 或者 Object[] 类型的 List.
	 * 
	 * 也可以在实体类中创建对应的构造器, 然后在 JPQL 语句中利用对应的构造器返回实体类的对象.
	 */
	@Test
	public void testPartlyProperties_v1(){

	    //默认情况下, 若只查询部分属性, 则将返回 Object[] 类型的结果. 或者 Object[] 类型的 List.
        String jpql = "SELECT c.lastName, c.age FROM Customer c WHERE c.id > ?";
        List<Object[]> result = entityManager.createQuery(jpql).setParameter(1, 1).getResultList();

        System.out.println(result);
	}
    
    /**
	 * 可以 new 一个类的构造器,让 JPA 内部通过这个构造器创建这个类的实例。
	 * 需要注意的是:
	 *    构造器的参数数量、类型一定要对应上。
	 *    new 的类是不是实体类都行,有可能不行(报错)就要写全类名
	 */
    @Test
	public void testPartlyProperties_v2(){

	    //也可以在实体类中创建对应的构造器, 然后再 JPQL 语句中利用对应的构造器返回实体类的对象.
        String jpql = "SELECT new Customer(c.lastName, c.age) FROM Customer c WHERE c.id > ?";
        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
        List result = entityManager.createQuery(jpql).setParameter(1, 1).getResultList();

        System.out.println(result);
	}
    /**
     * new map 的方式让JPA内部创建Map。
     */
    @Test
	public void testPartlyProperties_v3(){
        // new map 的方式让JPA内部创建Map。 如果不写别名,Map的 key 就是下标(索引位置)
	    String jpql = "SELECT new map(c.age as 我是map的key1,c.lastName as 我是map的key2) FROM Customer c WHERE c.age > :ageParam AND c.lastName LIKE :lastNameParam";
	    Query query = entityManager.createQuery(jpql);

	    query.setParameter("ageParam", 1);
	    query.setParameter("lastNameParam", "%TEST%");
	    List<java.util.Map<String,Object>> customers = query.getResultList();
	    System.out.println(customers.size());
	    System.out.println(customers);
	}
}
5.3.5、分页查询

原生的JPA可以使用Query setMaxResults(int maxResult) 结合Query setFirstResult(int startPosition) 使用可实现分页查询。前面有对 setMaxResults(…) 方法的介绍,点击查看。

5.3.6、命名查询(名称查询)
//这个报错没关系,说是要加上SELECT、UPDATE的。    就算加了还是报错,因为有问号,他说要将参数写上
@NamedQuery(name="testNamedQuery", query="FROM Customer c WHERE c.id = ?")
@Table(name="jpa_cutomers")
@Entity
public class Customer {
	
	private Integer id;
	private String lastName;
	private String email;
	private int age;
	
	private Date birth;
	private Date createTime;
    
    // 写上 getter、setter方法。根据需要来编写toString()、构造器
}

测试代码

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	/**
     * createNamedQuery 适用于在实体类前使用 @NamedQuery 标记的查询语句   
     *   需要在实体类上  @NamedQuery(name="testNamedQuery", query="FROM Customer c WHERE c.id = ?")
     *                  也可以写成@NamedQuery(name="testNamedQuery", query="SELECT c FROM Customer c WHERE c.id = ?")
     */
    @Test
    public void testNamedQuery(){
        //如果数据库没有对应的记录,将会报异常NoResultException
        Query query = entityManager.createNamedQuery("testNamedQuery").setParameter(1, 100);
        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
        Customer customer = (Customer) query.getSingleResult();

        System.out.println(customer);
    }
}
5.3.7、关联查询

关联查询在实际开发中是经常会遇到的一种情况。

如果说在实体类上做好了关联关系的映射,实际上也不怎么需要用到JPQL的方式进行关联查询。(就是说@ManyToOne 或者 @ManyToMany 用对了,用好了,就不怎么需要去用JPQL了,直接使用 find(…) 或者 getReference (…) 就能通过导航对象的方式做到很好的关联查询了,就是通过find(…) 或者 getReference (…) 得到的对象使用使用getxxx()方法就能获取到关联的另外一方的数据了)

在JPQL中,很多时候都是通过在实体类中配置实体关联的类属性来实现隐含的关联(join)查询。例如:select o from Orders o where o.address.streetNumber=2000 ,这个JPQL语句编译成SQL时就会自动包含关联,默认为左关联。

在某些情况下可能仍然需要对关联做精确的控制。为此,JPQL 也支持和 SQL 中类似的关联语法。如:

  • left outer join fetch / left join fetch (这两个有没有 outer 都是一样的)
  • left outer join / left join (这两个有没有 outer 都是一样的)
  • inner join fetch
  • inner join
5.3.7.1、左连接查询

在 JPA 中使用 jpql 做左连接查询又分为迫切左外连接普通的左连接

  • 迫切左外连接。需要用 left outer join fetch / left join fetch 关键字来关联对象。需要使用@OneToMany 、@ManyToOne注解做好关联关系。示例如下:

    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    import com.xxx.jpademo.jpademo01.pojo.Customer;
    
    public class JPATest {
    
    	private EntityManagerFactory entityManagerFactory;
    	private EntityManager entityManager;
    	private EntityTransaction transaction;
    	
    	@Before
    	public void init(){
    		//1. 创建 EntitymanagerFactory
    		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
    		//2. 创建 EntityManager. 
    		entityManager = entityManagerFactory.createEntityManager();
    		//3. 开启事务
    		transaction = entityManager.getTransaction();
    		transaction.begin();
    	}
    	
    	@After
    	public void destroy(){
    		//5. 提交事务
    		transaction.commit();
    		//6. 关闭 EntityManager
    		entityManager.close();
    		//7. 关闭 EntityManagerFactory
    		entityManagerFactory.close();
    	}
    	
    	/**
         * 迫切左外连接: (迫切的:左连接形式的sql语句,然后直接实例化多的一端的集合)
         * LEFT JOIN FETCH 关键字表示迫切左外连接检索策略。
         * 注意:
         *     需要使用@OneToMany 、@ManyToOne注解做好关联关系。
         *     使用 FETCH 可以用实体类对象接收返回值,并且初始化关联的集合。
         *     如果不用 FETCH 的连接要用List<Object[]>接收返回值。不用 FETCH 就不是迫切左外连接了。
         *     查询结果中可能会包含重复元素, 可以通过SELECT DISTINCT 关键字去重复,也可以通过一个 HashSet 来过滤重复元素。
         */
    	@Test
    	public void testLeftOuterJoinFetchV1(){
    	    /**
    		 * LEFT OUTER JOIN FETCH c.orders 可以看到就是 用左连接 去连接自己类当中的 orders 属性。也就是说做了一对多处理下写的这个JPQL
    	     * 这样就是发送一条 sql 就能将两个关联的数据信息一起查出并赋值到Customer对象。
    	     * 如果是常规的 FROM Customer c WHERE c.id = ? 查询 或者 entityManager.find(Customer.class, 100); 由于一对多用了 @OneToMany 默认是懒加载,发送两条sql
    	     * 我们希望一条 左连接sql 查询出信息并赋值给一个对象, 记得一定要加 FETCH 关键字;不加的话返回值将会是一个list<Object[]> 
    		 */
    		String jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.id = ?";
    
    	    Customer customerObj =  (Customer) entityManager.createQuery(jpql).setParameter(1, 16).getSingleResult();
    	    
    	    System.out.println(customerObj.getLastName());
    	    System.out.println(customerObj.getOrders().size());
            
    
            // 查询一个集合的话,结果集中可能会包含重复元素,因为一对多嘛。可以通过SELECT DISTINCT 关键字去重复,也可以通过一个 HashSet 来过滤重复元素。
            jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.lastName like ?";
    
    	    List<Customer> customerList =  (List<Customer>) entityManager.createQuery(jpql).setParameter(1, "%1对多%").getResultList();
    	    
    	    for (Customer customer : customerList) {
    	    	System.out.println(customer.getLastName()+"--"+customer.getOrders().size()); 
    		}
    	}
        
        /**
    	 * 去重复方式1 (推荐。不用写什么java代码了)。
    	 * 一般情况下对于业务的需求都是需要一组不重复的对象集合,然后集合里每个对象都携带着关联的另外一方的对象集合。(就像是一带多)。
    	 * 左连接的查询是可能会出现重复数据的情况,所以就需要去重复了
    	 */
        @Test
    	public void testLeftOuterJoinFetchV2(){
    	    // 使用上 SELECT DISTINCT 关键字。 c 只是表示别名。 这个c.orders是实体类(Customer)中的 orders 属性
    		String jpql = "SELECT DISTINCT c FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.lastName like ?";
    	    
            List<Customer> customer =  (List<Customer>) entityManager.createQuery(jpql).setParameter(1, "%1对多%").getResultList();
    	    for (Customer customer : customerList) {
    	    	System.out.println(customer.getLastName()+"--"+customer.getOrders().size()); 
    		}
    	}
        
        /**
    	 * 去重复方式2  (编写 java 代码的方式去除重复)
    	 * 一般情况下对于业务的需求都是需要一组不重复的对象集合,然后集合里每个对象都携带着关联的另外一方的对象集合。(就像是一带多)。
    	 * 左连接的查询是可能会出现重复数据的情况,所以就需要去重复了
    	 */
        @Test
    	public void testLeftOuterJoinFetchV2(){
    		// c 只是表示别名。 这个c.orders是实体类(Customer)中的 orders 属性
    		String jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.lastName like ?";
    
    	    List<Customer> customerList =  (List<Customer>) entityManager.createQuery(jpql).setParameter(1, "%1对多%").getResultList();
    	    
    	    //去重复逻辑    内存去重
    	    customerList = new java.util.ArrayList<Customer>(new java.util.LinkedHashSet<Customer>(customerList));
    	    
    	    for (Customer customer : customerList) {
    	    	System.out.println(customer.getLastName()+"--"+customer.getOrders().size()); 
    		}
    	    
    	}
    }
    
  • 普通左外连接:普通左外连接。需要用 left outer join / left join 关键字来关联对象。需要使用@OneToMany 、@ManyToOne注解做好关联关系(hibernate 和 jpa 的 5.1.17.Final 以后版本可以不写这两个注解,也就是可以不做对象关联关系)。普通左外连接默认返回 Object[] 类型的结果. 或者 Object[] 类型的 List。示例如下:

    import java.util.Date;
    import java.util.Iterator;
    import java.util.List;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    import javax.persistence.Query;
    
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    import com.xxx.jpademo.jpademo01.pojo.Customer;
    import com.xxx.jpademo.jpademo01.pojo.Order;
    
    public class JPATest {
    
    	private EntityManagerFactory entityManagerFactory;
    	private EntityManager entityManager;
    	private EntityTransaction transaction;
    	
    	@Before
    	public void init(){
    		//1. 创建 EntitymanagerFactory
    		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
    		//2. 创建 EntityManager. 
    		entityManager = entityManagerFactory.createEntityManager();
    		//3. 开启事务
    		transaction = entityManager.getTransaction();
    		transaction.begin();
    	}
    	
    	@After
    	public void destroy(){
    		//5. 提交事务
    		transaction.commit();
    		//6. 关闭 EntityManager
    		entityManager.close();
    		//7. 关闭 EntityManagerFactory
    		entityManagerFactory.close();
    	}
    	
        /**
         * 普通的左连接就是用 left outer join 或者 left join 关键字连接查询,和迫切左外连接相比它不要了 FETCH 关键字 
         * 默认返回 Object[] 类型的结果. 或者 Object[] 类型的 List.
         */
    	@Test
    	public void testLeftOuterJoinV1(){
    	    // 不加 FETCH 的话返回值将会是一个list<Object[]> 
    	    String jpql = "FROM Customer c LEFT OUTER JOIN c.orders WHERE c.lastName like ?";
    
    	    // Customer 类中@OneToMany注解不要加 fetch = FetchType.EAGER, 就会是一个左连接的sql,不然会出现两个sql。这样是不好的
    	    List<Object[]> result = entityManager.createQuery(jpql).setParameter(1, "%1对多%").getResultList();
    	    System.out.println(result);
    	}
        
        /**
    	 * hibernate 和 jpa 的 5.1.17.Final 以后版本可以直接连接两个没有关联关系的实体类(持久化类)
    	 *
    	 * 这样可以不用使用 @OneToMany 、@ManyToOne 等关联注解就能做连接查询。当前版本为 4.2.4.Final 测试过不行
    	 */
        @Test
    	public void testLeftOuterJoinV1(){
    		// hibernate 和 jpa 的 5.1.17.Final 以后版本可以直接连接两个实体类
    	    String jpql = "SELECT c.lastName,o.orderName FROM Customer c LEFT OUTER JOIN Order o ON c.id = o.customerId WHERE c.lastName like ?";
    	    
    	    List<Object[]> result = entityManager.createQuery(jpql).setParameter(1, "%1对多%").getResultList();
    	    System.out.println(result);
    	}
        
        
    	
        /**
         * 返回 Object[] 数组的集合对数据操作不是那么的友好。 Object[] 数组处理方式一:
         * 
    	 * 可以 new 一个类的构造器,让 JPA 内部通过这个构造器创建这个类的实例。
    	 * 需要注意的是:
    	 *    构造器的参数数量、类型一定要对应上。
    	 *    new 的类是不是实体类都行,有可能不行(报错)就要写全类名
    	 */
        @Test
    	public void testLeftOuterJoinV2(){
    
    	    //也可以在某个类中创建对应的构造器, 然后在 JPQL 语句中利用对应的构造器返回某个类的对象.
            String jpql = "SELECT new 一个类的构造器(c.属性, o.属性, ...) FROM Customer c LEFT OUTER JOIN c.orders WHERE c.lastName like ?";
            List<某个类> result = (List<某个类>)entityManager.createQuery(jpql).setParameter(1, "%%").getResultList();
    
            System.out.println(result);
    	}
        
        /**
         * 返回 Object[] 数组的集合对数据操作不是那么的友好。 Object[] 数组处理方式二:
         * 可以使用 new map 的方式让JPA内部创建Map。
         * 连表方式创建Map有两种方式:
         *     1、使用属性创建Map;    例如:SELECT new map(c.lastName as key1,o.orderName as key2) FROM ...
         *     2、直接使用对象创建Map; 例如:SELECT new map(c as key1,o as key2) FROM c LEFT JOIN c.orders o ...
         */
    	@Test
    	public void testLeftOuterJoinV3(){
    		// new map 的方式让JPA内部创建Map。 如果不写别名,Map的 key 就是下标(索引位置)
    	    String jpql = "SELECT new map(c.lastName as 我是map的key1,o.orderName as 我是map的key2) FROM Customer c LEFT OUTER JOIN c.orders o WHERE c.lastName like ?";
    
    	    List<java.util.Map<String,Object>> result = entityManager.createQuery(jpql).setParameter(1, "%1对多%").getResultList();
    	    System.out.println(result);
    	}
        @Test
    	public void testLeftOuterJoinV1(){
    		// new map 的方式让JPA内部创建Map。将整个查询到的对象装进Map 。 如果不写别名,Map的 key 就是下标(索引位置)
    	    String jpql = "SELECT new map(c as 我是map的key1,o as 我是map的key2) FROM Customer c LEFT OUTER JOIN c.orders o WHERE c.lastName like ?";
    
    	    List<java.util.Map<String,Object>> result = entityManager.createQuery(jpql).setParameter(1, "%1对多%").getResultList();
    	    System.out.println(result);
    	}
        
        /**
    	 * 去重复方式 (推荐。不用写什么java代码了)。
    	 * 一般情况下对于业务的需求都是需要一组不重复的对象集合,然后集合里每个对象都携带着关联的另外一方的对象集合。(就像是一带多)。
    	 * 左连接的查询是可能会出现重复数据的情况,所以就需要去重复了
    	 */
        @Test
    	public void testLeftOuterJoinV1(){
    		// 使用上 SELECT DISTINCT 关键字去除重复。 c 只是表示别名。 这个c.orders是实体类(Customer)中的 orders 属性
    	    String jpql = "SELECT DISTINCT c.lastName,o.orderName FROM Customer c LEFT OUTER JOIN c.orders o WHERE c.lastName like ?";
    	    
    	    List<Object[]> result = entityManager.createQuery(jpql).setParameter(1, "%1对多%").getResultList();
    	    System.out.println(result);
    	}
        
    }
    

总结:

  • 迫切左外连接使用 LEFT JOIN FETCH 关键字;普通的左外连接使用 LEFT JOIN 关键字。
  • 在迫切左外连接的 JPQL 语句中,无论是否使用 SELECT 关键字,query.list() 方法返回的集合中存放的是实体类(持久化类)对象的引用;普通的左外连接在 JPQL 语句中,当使用了 SELECT 关键字, query.list() 方法返回的集合中存放的是实体类(持久化类)对象的引用,如果在 JPQL 语句中没有使用 SELECT 关键字, query.list() 方法返回的结果是一个Object数组类型的集合
  • 迫切左外连接的方式会以左连接形式的sql语句进行查询,同时将查询到的数据把有关联的 java 对象、集合一次性全部都给初始化实例化并赋值。普通的左连接如果在 JPQL 语句中用了 SELECT 关键字,会以左连接形式的sql语句进行查询,但只会实例化主表对应的 java 类,关联的另外一方会在使用到的时候再次发送sql语句去查询,如果在 JPQL 语句中没有使用 SELECT 关键字,会以左连接形式的sql语句进行查询,会将所有关联的对象一次性全部查出来,并且以Object数组的集合返回。
  • 迫切左外连接可以使用 SELECT DISTINCT 关键字去重复,也可以使用 new ArrayList<xxx>(new LinkedHashSet<xxx>(parameter)); 的方式去除重复。而普通的左外连接可以使用 SELECT DISTINCT 关键字去重复,但却不能使用 new ArrayList<xxx>(new LinkedHashSet<xxx>(parameter)); 的方式去除重复。
5.3.7.2、内连接查询

内连接的查询方式就是将左连接的关键字改成inner join fetch 或者 inner join 其他的一样。

5.3.8、子查询

JPQL也支持子查询,在 where 或 having 子句中可以包含另一个查询。

import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;
import com.xxx.jpademo.jpademo01.pojo.Order;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	@Test
	public void testSubQuery(){

	    /*
		 * JPQL也支持子查询,在 where 或 having 子句中可以包含另一个查询。当子查询返回多于 1 个结果集时,
		 * 它常出现在 any、all、exists表达式中用于集合匹配查询。它们的用法与SQL语句基本相同。
		 */

	    //查询所有 Customer 的 lastName 为FFF 的 Order
	    String jpql = "SELECT o FROM Order o WHERE o.customer = (SELECT c FROM Customer c WHERE c.lastName = ?)";

	    Query query = entityManager.createQuery(jpql).setParameter(1, "FFF");
        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
	    List<Order> orders = query.getResultList();
	    System.out.println(orders.size());
	}
   
}
5.3.9、JPQL内建的函数

JPQL提供了以下一些内建函数,包括字符串处理函数、算术函数和日期函数。

import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;
import com.xxx.jpademo.jpademo01.pojo.Order;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	@Test
	public void testJpqlFunction(){

        /**
         * 使用 jpql 内建的函数
         * 
         * JPQL提供了以下一些内建函数,包括字符串处理函数、算术函数和日期函数。
         *     字符串处理函数主要有:
         *         concat(String s1, String s2):字符串合并/连接函数。
         *         
         *         substring(String s, int start, int length):取字串函数。
         *         
         *         trim([leading|trailing|both,] [char c,] String s):从字符串中去掉首/尾指定的字符或空格。
         *         
         *         lower(String s):将字符串转换成小写形式。
         *         
         *         upper(String s):将字符串转换成大写形式。
         *         
         *         length(String s):求字符串的长度。
         *         
         *         locate(String s1, String s2[, int start]):从第一个字符串中查找第二个字符串(子串)出现的位置。若未找到则返回0。
         * 
         *     算术函数主要有 abs、mod、sqrt、size 等。Size 用于求集合的元素个数。
         *     
         *     日期函数主要为三个,即 current_date、current_time、current_timestamp,它们不需要参数,返回服务器上的当前日期、时间和时戳。
         */

        String jpql = "SELECT lower(c.email) FROM Customer c";
         /**
          * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
          * 如果是返回多个就要使用 (List<...>)query.getResultList();
          */
        List<String> emails = entityManager.createQuery(jpql).getResultList();
        System.out.println(emails);
	}
}

5.4、原生SQL查询方式

JPA支持原生 SQL 的查询方式,一般是用来只有在性能或其他特定需求要求时,才推荐使用原生SQL查询。因为使用原生SQL查询时,你失去了JPA提供的许多好处,例如:延迟加载 和 缓存 等等。

import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;
import com.xxx.jpademo.jpademo01.pojo.Order;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	
	//createNativeQuery 适用于本地 SQL
    
    /**
     * 查询单个列
     */
	@Test
	public void testNativeQuery_v1(){
	    //如果数据库没有对应的记录,将会报异常NoResultException
	    String sql = "select age from jpa_cutomers where id = ?";
	    Query query = entityManager.createNativeQuery(sql).setParameter(1, 16);
         /**
          * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
          * 如果是返回多个就要使用 (List<...>)query.getResultList();
          */
	    Object result = query.getSingleResult();
	    System.out.println(result.getClass());// 只是查询一个字段,数据库类型对应的java是什么类型就是什么类型。如:数据库是int,java就是Integer
	}
	
    /**
     * 查询多个列、多个字段,返回的是Object[] 类型 或者 Object[] 类型的List
     */
	@Test
	public void testNativeQuery_v2(){
	    //如果数据库没有对应的记录,将会报异常NoResultException
	    String sql = "select last_name,age from jpa_cutomers where id = ?";
	    Query query = entityManager.createNativeQuery(sql).setParameter(1, 16);
         /**
          * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
          * 如果是返回多个就要使用 (List<...>)query.getResultList();
          */
	    Object result = query.getSingleResult();
	    System.out.println(result.getClass());// 只是查询多个字段,返回的是Object[] 类型 或者 Object[] 类型的List
	}
	
	@Test
	public void testNativeQuery_v3(){
	    //如果数据库没有对应的记录,将会报异常NoResultException
	    String sql = "select * from jpa_cutomers where id = ?";
	    
	    /**
	     * 查询多个字段,返回的是Object[] 类型 或者 Object[] 类型的List,这样的数据是不好操作的,可以像下面的方式操作。
	     * 
	     * Object[] 类型的数据不好操作,如果查询的数据库表正好可以对应某个实体类,可以在执行SQL时,加上实体类类型的参数。
	     */
	    Query query = entityManager.createNativeQuery(sql,Customer.class).setParameter(1, 16);
         /**
          * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
          * 如果是返回多个就要使用 (List<...>)query.getResultList();
          */
	    Customer result = (Customer)query.getSingleResult();//因为设置了实体类型,所以查询的结果就可以用实体类接收。
	    System.out.println(result);
	    
	    /**
	     * 它还有个特征:如果查询的这个实体类里面做了对象关联,也就是用了@OneToMany、@ManyToOne之类的注解的,
	     * 然后通过这个查询到的对象获取关联的另外一方的信息,它会再次发送新的一个sql语句去查询关联的另外一方信息,而且是以懒加载的方式查询。
	     */
	    System.out.println(result.getOrders());
	}
	
	@Test
	public void testNativeQuery_v4(){
	    //如果数据库没有对应的记录,将会报异常NoResultException
	    String sql = "select * from jpa_cutomers a left join jpa_orders b on a.id = b.customer_id where a.id = ?";
	    
	    /**
	     * 查询多个字段,返回的是Object[] 类型 或者 Object[] 类型的List,这样的数据是不好操作的,可以像下面的方式操作。
	     * 
	     * Object[] 类型的数据不好操作,如果查询的数据库表正好可以对应某个实体类,可以在执行SQL时,加上实体类类型的参数。
	     */
	    Query query = entityManager.createNativeQuery(sql,Customer.class).setParameter(1, 16);

	    
	    // 这个左连接查询确实能让结果封装为一个对应的实体类,但是他是不会用查到的数据去实例化关联的另外一方的集合或者对象的。
	    Customer result = (Customer)query.getSingleResult();
	    System.out.println(result);
	    
	    /**
	     * 通过 原生的左连接sql 去查询得到的实体类对象,然后通过这个实体类对象去获取关联的另外一方的数据,它还是会重新发送一个sql语句去查询。
	     * 相当于上面的左连接查询然后设置一个实体类没用,这样要么可以用Object[]接收,或者不用写原生的左连接,就直接查单个表,关联的另外一方调用属性的时候自然会得到
	     */
	    System.out.println(result.getOrders());
	}
    
    @Test
	public void testNativeQuery_v5(){
	    
	    String sql = "select * from jpa_cutomers where last_name like ?";
	    Query query = entityManager.createNativeQuery(sql).setParameter(1, "%1对多%");
		// 返回的是 Object[] 的List,如果不想用 Object[],可以 createNativeQuery(sql,写一个实体类的Class)
	    List<Object[]> result = (List<Object[]>)query.getResultList();
	    for (Object[] object : result) {
	    	System.out.println(object);
		}
	}
}

5.5、QBC方式查询(CriteriaQuery方式)

这种方式是一种纯操作对象的方式来操作数据库数据。

这种方式先认识重要的3个对象:

  1. Root< ?> root

    • 这个root就相当于查询和操作的实体对象的根,可以通过 Path get(xx) 的方法,来获取需要操作的字段。
  2. CriteriaQuery<?> query

    • 用它可以创建出 SQL 查询语句的各个关键部分,比如select、from、where、group by 、Order by、distinct等。

    • 内置的一些方法如下:

      public interface CriteriaQuery<T> extends AbstractQuery<T> {
          
          CriteriaQuery<T> select(Selection<? extends T> var1);
          
          CriteriaQuery<T> multiselect(Selection<?>... var1);
          
          CriteriaQuery<T> multiselect(List<Selection<?>> var1);
          
          CriteriaQuery<T> where(Expression<Boolean> var1);
          
          CriteriaQuery<T> where(Predicate... var1);
          
          CriteriaQuery<T> groupBy(Expression<?>... var1);
          
          CriteriaQuery<T> groupBy(List<Expression<?>> var1);
          
          CriteriaQuery<T> having(Expression<Boolean> var1);
          
          CriteriaQuery<T> having(Predicate... var1);
          
          CriteriaQuery<T> orderBy(Order... var1);
          
          CriteriaQuery<T> orderBy(List<Order> var1);
          
          CriteriaQuery<T> distinct(boolean var1);
          
          List<Order> getOrderList();
          
          Set<ParameterExpression<?>> getParameters();
      }
      
  3. CriteriaBuilder builder

    • CriteriaBuilder是用来构建CritiaQuery<?>的构建对象,可以用来创建条件比较或者条件组合。一般使用如下:

      public void testNativeQuery_v1(){
          CriteriaBuilder builder = entityManager.getCriteriaBuilder();
          ...
          Predicate predicate1 = builder.equal(root.get("sex"),"男"); // 创建一个等于条件
          Predicate predicate2 = builder.like(root.get("name"),"%TEST%");// 创建一个 like 条件
          Predicate predicate3 = builder.greaterThanOrEqualTo(root.get("age"),22);// 创建一个 大于或等于 条件
          builder.and(predicate1,predicate2,predicate3);// 使用and连接条件
          builder.or(...); // 使用and连接条件
          ...
      }
      
5.5.1、查询单个实体对象或者集合
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	/**
	 * 查询单个对象
	 */
	@Test
	public void testQBCQuery_v1(){
        // 获取条件构建器
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        
        /**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 */
		CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
        
        // 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);

		//query.select( root ); 这句话要不要都一样
        
		query.where( builder.equal( root.get( "id" ), 16 ) );//写的是类的属性名,id这一列等于16的意思

        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
		Customer customerResult = entityManager.createQuery( query ).getSingleResult();
		
		System.out.println(customerResult);
		
		System.out.println(customerResult.getOrders());
	}
    /**
	 * 查询实体对象集合
	 */
    @Test
	public void testQBCQuery_v2(){
        // 获取条件构建器
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        /**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 */
		CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
        
        // 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);

		//query.select( root ); 这句话要不要都一样
        
		query.where( builder.like( root.get( "lastName" ), "%TEST%" ) );//写的是类的属性名,id这一列等于16的意思

        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
		List<Customer> customers = entityManager.createQuery( query ).getResultList();
		
		System.out.println(customers);
		
	}
}
5.5.2、查询对象的某个属性(某个列)
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;
public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}

	@Test
	public void testQuerySingleAttr(){
        // 获取条件构建器
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        /**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 */
		CriteriaQuery<String> query = builder.createQuery(String.class);
        
        // 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);
        
        // 通过根对象获取要查询的属性,写的是类的属性名。也就是要查询的表中的列
        // 选择实体的某个属性。写的是类的属性名
		query.select( root.get("lastName") );
        
		query.where( builder.equal( root.get( "id" ), 16 ) );//写的是类的属性名 id这一列等于16的意思

        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
		String result = entityManager.createQuery( query ).getSingleResult();
		
		System.out.println(result);
		
	}
}
5.5.3、查询对象的部分属性(多个列)
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.Tuple;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;
public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}

    /**
	 * 默认情况下, 若只查询部分属性, 则将返回 Object[] 类型的结果. 或者 Object[] 类型的 List.
	 */
	@Test
	public void testQueryMultipleAttr_v1(){
        // 获取条件构建器
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        /**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 */
		CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
        
        // 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);

        // 通过根对象获取要查询的属性,写的是类的属性名。也就是要查询的表中的列
		Path<String> lastName = root.get("lastName");
		Path<Integer> age = root.get("age");
		
        // 要查询的多个列,将要查询的列放入到这个select方法里面。前面写了Object[]这里要写select方法
		query.select( builder.array(lastName,age) );
		query.where( builder.equal( root.get( "id" ), 16 ) );

        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
		Object[] result = (Object[])entityManager.createQuery( query ).getSingleResult();
		
		System.out.println(result);
	}
    
    /**
      * Object[]的数据操作起来不是那么的友好。
	  * 可以使用 javax.persistence.Tuple 来处理
	  * 
	  */
    @Test
	public void testQueryMultipleAttr_v2(){
        // 获取条件构建器
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        /**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 */
		CriteriaQuery<Tuple> query = builder.createQuery(Tuple.class);
        
        // 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);

        // 通过根对象获取要查询的属性,写的是类的属性名。也就是要查询的表中的列
		Path<String> lastName = root.get("lastName");
		Path<Integer> age = root.get("age");
		
        // 要查询的多个列,将要查询的列放入到这个multiselect方法里面。前面设定了泛型不是Object[]所以这里要用multiselect方法
		query.multiselect(  lastName, age  );
		query.where( builder.like( root.get( "lastName" ), "%TEST%" ) );

        /**
         * 如果确定返回单个结果可以使用 query.getSingleResult() 方法获取单个结果。如果数据库没有对应的记录,将会报异常NoResultException
         * 如果是返回多个就要使用 (List<...>)query.getResultList();
         */
		List<Tuple> tuples = (List<Tuple>)entityManager.createQuery( query ).getResultList();
		
		for ( Tuple tuple : tuples ) {
			String lastNameResult = tuple.get( lastName );
			Integer ageResult = tuple.get( age );
			System.out.println(lastNameResult);
			System.out.println(ageResult);
		}
		
		// or using indices。 两者选其一就好
		for ( Tuple tuple : tuples ) {
			String lastNameResult = (String) tuple.get( 0 );
			Integer ageResult = (Integer) tuple.get( 1 );
			System.out.println(lastNameResult);
			System.out.println(ageResult);
			
		}
    }
    
     /**
      * Object[]的数据操作起来不是那么的友好。
	  * 可以给一个类写一个构造器,让 JPA 内部通过这个构造器创建这个类的实例。
	  * 需要注意的是:
	  *    构造器的参数数量、类型一定要对应上。
	  *    类是不是实体类都行
	  */
    @Test
	public void testQueryMultipleAttr_v3(){
        // 获取条件构建器
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        
        /**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 *
		 * 在这个例子里:参数和泛型写的是合适构造器的那个类,是不是实体类都行,关键是构造器合适
		 */
		CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
        
        // 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);

		Path<String> lastName = root.get("lastName");
		Path<Integer> age = root.get("age");
		
        // 通过这个 builder.construct() 方法,JPA 会通过某个类的构造器创建一个类的对象。要和builder.createQuery( class )写得一样
		query.select( builder.construct( Customer.class, lastName, age ) );// 要查询的多个列,将要查询的列放入到这个select方法里面
		query.where( builder.equal( root.get( "id" ), 16 ) );

		Customer result = (Customer)entityManager.createQuery( query ).getSingleResult();
		
		System.out.println(result);
    }
    
}
5.5.5、多条件拼接
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Tuple;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	@Test
	public void testMultipleConditionsQuery_v1(){
        // 获取条件构建器
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        
        /**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 * 
		 * 这里的代码示例只是查询其中的几个列,所以使用Tuple做泛型
		 */
		CriteriaQuery<Tuple> query = builder.createQuery(Tuple.class);
        
        // 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);

        // 通过根对象获取要查询的属性,写的是类的属性名。也就是要查询的表中的列
		Path<String> lastName = root.get("lastName");
		Path<Integer> age = root.get("age");
        // 要查询的多个列,将要查询的列放入到这个multiselect方法里面。前面设定了泛型不是Object[]所以这里要用multiselect方法
		query.multiselect(  lastName, age  );
		
		
		Predicate p1 = builder.like( root.get( "lastName" ), "%TEST%" );// 创建一个 like 条件
		Predicate p2 = builder.greaterThanOrEqualTo(root.get("age"), 7);// 创建一个 >= 条件
		Predicate andConditions =  builder.and(p1,p2);// 将前面的两个条件用and来连接起来
		
		query.where( andConditions );// 这个意思是where后面的所有条件

		List<Tuple> tuples = (List<Tuple>)entityManager.createQuery( query ).getResultList();
		
		for ( Tuple tuple : tuples ) {
			String lastNameResult = tuple.get( lastName );
			Integer ageResult = tuple.get( age );
			System.out.println(lastNameResult);
			System.out.println(ageResult);
		}
	}
}
5.5.6、关联查询
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Fetch;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;
import com.xxx.jpademo.jpademo01.pojo.Order;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	/**
	 * 使用 root.join( "属性" ); 方法进行关联查询。
	 * 
	 * 这个方式有懒加载特征,他的查询方式是 join 两个表,然后查询的列、属性、字段是 Root 根的那个实体类。当用到关联的另外一方的属性的时候,再次发送SQL语句查询。
	 */
	@Test
	public void testJoinQuery_v1(){
		// 获取条件构建器
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		
		/**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 */
		CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
		
		// 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);

		/**
		 * 泛型的第一个是当前查询的Entity,第二个是关联的Entity。需要在类里面做好关联映射,也就是使用@OneToMany、@ManyToOne等注解做好关联映射
		 * 
		 * 默认是 inner join,想要改为 left join 可以多加一个参数 --> JoinType.LEFT。
		 */
		Join<Customer, Order> customerJoin = root.join( "orders" );
		
		
		Predicate p1 = builder.like( root.get( "lastName" ),"%1对多%" ); // 创建一个 like 条件
		Predicate p2 = builder.equal( customerJoin.get("orderName"),"xxx" );// 创建一个 = 条件

		Predicate and = builder.and(p1,p2);// 将前面的两个条件用and来连接起来
		
		query.where( and );// 这个意思是where后面的所有条件

		List<Customer> customers = entityManager.createQuery( query ).getResultList();
		
		System.out.println(customers);
		//System.out.println(customers.get(0).getOrders());
	}
	
	/**
	 * 使用 root.fetch( "属性" ); 方法进行关联查询。这个没有懒加载特征,这个没有懒加载特征,这个没有懒加载特征
	 * 
	 * 这个方式没有懒加载特征,他的查询方式是 join 两个表,然后查询关联的所有的实体类里面的属性、字段、列,一次性将关联的类的属性都查出来。
	 */
	@Test
	public void testJoinQuery_v2(){
		// 获取条件构建器
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		
		/**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct等。
		 */
		CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
		
		// 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Customer> root = query.from(Customer.class);
		
		/**
		 * 泛型的第一个是当前查询的Entity,第二个是关联的Entity。需要在类里面做好关联映射,也就是使用@OneToMany、@ManyToOne等注解做好关联映射
		 * 
		 * 默认是 inner join,想要改为 left join 可以多加一个参数 --> JoinType.LEFT。
		 */
		Fetch<Customer, Order> customerFetch = root.fetch( "orders" );
		
		// 这里强转为 Join<xx,yy> 是为了获取关联对象的属性,然后用于关联对象的字段操作。比如用于 where 条件后面的筛选等。相当于得到关联的实体对象的根。
		// 在这里进行强转是考虑到可能需要用到 Fetch<xx,yy> 里面的特有方法。
		Join<Customer, Order> customerJoin = (Join<Customer, Order>) customerFetch;
		
		Predicate p1 = builder.like( root.get( "lastName" ),"%1对多%" );     // 创建一个 like 条件
		Predicate p2 = builder.equal( customerJoin.get("orderName"),"xxx" );// 创建一个 = 条件

		Predicate and = builder.and(p1,p2);// 将前面的两个条件用and来连接起来
		
		query.where( and );// 这个意思是where后面的所有条件

		List<Customer> customers = entityManager.createQuery( query ).getResultList();
		
		System.out.println(customers);
		//System.out.println(customers.get(0).getOrders());
	}
	
    /**
     * 添加多个join子句
     */
    public List<YourEntity> getEntitiesWithMultipleJoins() {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<YourEntity> query = cb.createQuery(YourEntity.class);
        
        Root<YourEntity> root = query.from(YourEntity.class);
        
        Join<YourEntity, RelatedEntity1> join1 = root.join("relatedEntity1", JoinType.INNER);
        Join<YourEntity, RelatedEntity2> join2 = root.join("relatedEntity2", JoinType.LEFT);
        Join<YourEntity, RelatedEntity3> join3 = root.join("relatedEntity3", JoinType.INNER);
        
        query.select(root).where(cb.equal(root.get("someProperty"), "someValue"));
        
        TypedQuery<YourEntity> typedQuery = entityManager.createQuery(query);
        return typedQuery.getResultList();
    }
}
5.5.7、子查询
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.xxx.jpademo.jpademo01.pojo.Customer;
import com.xxx.jpademo.jpademo01.pojo.Order;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	
	@Test
	public void testSubQuery_v1(){
		// 获取条件构建器
		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
		
		/**
		 * 通过条件构建器创建一个条件查询(CriteriaQuery)对象。泛型可以理解为要查询一个什么样的结果,例如:查询一个实体类就写实体类,查询其中几个列就写Object[]
		 * 
		 * 用它可以创建出 SQL 查询语句 的各个关键部分,比如select、from、where、group by 、Order by、distinct、子查询等。
		 */
		CriteriaQuery<Order> query = builder.createQuery(Order.class);
		
		// 这个root就相当于查询和操作的实体对象的根,可以通过 get(xx) 的方法,来获取需要操作的字段
        // 这个是必须的,得到这个 Root<实体类> 就能操作数据库表中的一些列,例如某一列是等于还是大于还是等于,
		Root<Order> root = query.from(Order.class);
		 
		/**
		 * 创建一个子查询,泛型可以理解为要查询一个什么样的结果,例如:子查询种要查询 id 而这个 id 的类型是Integer,这里就写Integer
		 */
		Subquery<Integer> subquery = query.subquery(Integer.class);
		
		// 获取子查询的根实体。
		Root<Customer> subroot = subquery.from(Customer.class);
		 
		// 设置子查询的选择条件
		subquery.select(subroot.get("id"));
		
		// 设置子查询的过滤条件
		subquery.where(builder.like(subroot.get("lastName"), "%1对多%"));
		 
		// 将子查询结果作为条件使用
		query.where(root.get("customer").get("id").in(subquery));
		 
		// 添加排序或其他查询条件
		query.orderBy(builder.asc(root.get("id")));
		 
		List<Order> results = entityManager.createQuery(query).getResultList();
		
		System.out.println(results);
	}
}

6、JPA执行存储过程

在这篇文章中用的是maven的方式并且引入的依赖是 hibernate-core 4.2.4.Final 、hibernate-entitymanager 4.2.4.Final 版本,这个版本引入的 JPA 是 2.0 版,这个 JPA 2.0 里面是不方便做存储过程调用操作,因为里面缺失了一些方法函数。将版本设为 4.3.11.Final 就行。

修改依赖版本:

<!-- hibernate 依赖 -->
  	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
		<version>4.3.11.Final</version>
	</dependency>
	
	<!-- 要用jpa就要加这个依赖 -->
	<dependency>
		<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-entitymanager</artifactId>
	    <version>4.3.11.Final</version>
	</dependency>
6.1、调用没有游标返回的存储过程

编写一个oracle存储过程为例

CREATE OR REPLACE PROCEDURE A_TEST_SP_ONE
(
  in_param_one in varchar2,
  out_param_one out number
)
AS
BEGIN
  select count(1) into out_param_one from a_test_one where name like in_param_one;
END A_TEST_SP_ONE;

JPA 代码调用示例:

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.ParameterMode
import javax.persistence.Persistence;
import javax.persistence.StoredProcedureQuery;

import org.hibernate.Session;
import org.hibernate.procedure.ProcedureCall;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    
    /**
     * 第一种方式:存储过程参数名称调用方式。
     */
	@Test
	public void testCallStoredProcedure_v1(){
        // 上面的那些事务啥的,可以不要的,这只是一个查询的存储过程。
        
        // 通过存储过程名 得到一个 StoredProcedureQuery 对象
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "A_TEST_SP_ONE");
        // 注册一个输入参数,ParameterMode.IN 表示输入参数
        query.registerStoredProcedureParameter( "in_param_one", String.class, ParameterMode.IN);
        // 注册一个输出参数,ParameterMode.OUT 表示输入参数
        query.registerStoredProcedureParameter( "out_param_one", Integer.class, ParameterMode.OUT);
        // 给 in_param_one 参数 设置一个输入值。
        query.setParameter("in_param_one", "%%");

        query.execute();// 执行存储过程。
        // 获取输出的结果
        Integer result = (Integer) query.getOutputParameterValue("out_param_one");
	}
    
    /**
     * 第二种方式:存储过程参数下标调用方式。
     */
	@Test
	public void testCallStoredProcedure_v2(){
        // 上面的那些事务啥的,可以不要的,这只是一个查询的存储过程。
        
        // 通过存储过程名 得到一个 StoredProcedureQuery 对象
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "A_TEST_SP_ONE");
        // 注册一个输入参数,ParameterMode.IN 表示输入参数
        query.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN);
        // 注册一个输出参数,ParameterMode.OUT 表示输入参数
        query.registerStoredProcedureParameter( 2, Integer.class, ParameterMode.OUT);
        // 给 下标1 的参数 设置一个输入值。
        query.setParameter(1, "%%");

        query.execute();// 执行存储过程。
        // 获取 下标2 的输出的结果
        Integer result = (Integer) query.getOutputParameterValue(2);
	}
    
    /**
     * 第三种方式:通过 hibernate 的 session对象,然后再通过存储过程参数名称调用方式
     */
	@Test
	public void testCallStoredProcedure_v3(){
        // 上面的那些事务啥的,可以不要的,这只是一个查询的存储过程。
        
        // 获取 hibernate 的 session 对象
        Session session = entityManager.unwrap(Session.class);
        
        // 通过存储过程名 得到一个 ProcedureCall 对象
        ProcedureCall procedureCall = session.createStoredProcedureCall("A_TEST_SP_ONE");
        
        // 注册一个输入参数,ParameterMode.IN 表示输入参数,并通过bindValue()绑定值
        procedureCall.registerParameter("in_param_one", String.class, ParameterMode.IN).bindValue("%%");
        // 注册一个输出参数,ParameterMode.OUT 表示输入参数
        procedureCall.registerParameter("out_param_one", Integer.class, ParameterMode.OUT);

        // 执行存储过程。 并且获取输出参数
        Integer result = (Integer)procedureCall.getOutputs().getOutputParameterValue("out_param_one");

        System.out.println(result);
	}
}
6.2、调用有游标输出的存储过程

编写一个oracle存储过程为例

CREATE OR REPLACE PROCEDURE A_TEST_SP_two
(
  in_param_one in varchar2,
  out_result_set out sys_refcursor
)
AS
BEGIN
  open personPhones for 
  select * from a_test_one where name like in_param_one;
END A_TEST_SP_ONE;

JPA 代码调用示例:

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.ParameterMode
import javax.persistence.Persistence;
import javax.persistence.StoredProcedureQuery;

import org.hibernate.Session;
import org.hibernate.procedure.ProcedureCall;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
    
    /**
     * 第一种方式:存储过程参数名称调用方式。
     */
	@Test
	public void testCallStoredProcedure_v1(){
        // 上面的那些事务啥的,可以不要的,这只是一个查询的存储过程。
        
        // 通过存储过程名 得到一个 StoredProcedureQuery 对象
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "A_TEST_SP_TWO");
        // 注册一个输入参数,ParameterMode.IN 表示输入参数
        query.registerStoredProcedureParameter( "in_param_one", String.class, ParameterMode.IN);
        // 注册一个输出参数,ParameterMode.OUT 表示输入参数。 写Class.class也行 或者 写Void.class也行
        query.registerStoredProcedureParameter( "out_param_one", Class.class, ParameterMode.REF_CURSOR);
        // 给 in_param_one 参数 设置一个输入值。
        query.setParameter("in_param_one", "%%");

        query.execute();// 执行存储过程。
        /*
         * 获取输出的结果。 如果配置了 oracle10G以下的方言是不支持通过存储过程来获取结果集的。
         * 
         * 在这个文章中引入的依赖是4.3.11.Final版本内置的Oracle方言最高就oracle10G,所以没办法,这里执行是报错的
         * 
         * 解决方法也有,升级Oracle,非要用Oracle10G也可以自定义方言,继承内置的那些方言的类然后做一下扩展。
         */
        List<Object[]> postComments = query.getResultList();
        // 不想用Object[]的话可以用下面这种方式。 得到结果集后在处理整个结果集。
        ResultSet result = (ResultSet)query.getOutputParameterValue("out_param_one");
	}
    
    /**
     * 第二种方式:存储过程参数下标调用方式。
     */
	@Test
	public void testCallStoredProcedure_v2(){
        // 上面的那些事务啥的,可以不要的,这只是一个查询的存储过程。
        
        // 通过存储过程名 得到一个 StoredProcedureQuery 对象
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "A_TEST_SP_TWO");
        // 注册一个输入参数,ParameterMode.IN 表示输入参数
        query.registerStoredProcedureParameter( 1, String.class, ParameterMode.IN);
        // 注册一个输出参数,ParameterMode.OUT 表示输入参数。 写Class.class也行 或者 写Void.class也行
        query.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR);
        // 给 下标1 的参数 设置一个输入值。
        query.setParameter(1, "%%");

        query.execute();// 执行存储过程。

        /*
         * 获取输出的结果。 如果配置了 oracle10G以下的方言是不支持通过存储过程来获取结果集的。
         * 
         * 在这个文章中引入的依赖是4.3.11.Final版本内置的Oracle方言最高就oracle10G,所以没办法,这里执行是报错的
         * 
         * 解决方法也有,升级Oracle,非要用Oracle10G也可以自定义方言,继承内置的那些方言的类然后做一下扩展。
         */
        List<Object[]> postComments = query.getResultList();
        // 不想用Object[]的话可以用下面这种方式。 获取 下标2 的输出的结果   得到结果集后在处理整个结果集。
        ResultSet result = (ResultSet) query.getOutputParameterValue(2);
	}
    
    /**
     * 第三种方式:通过 hibernate 的 session对象,然后再通过存储过程参数名称调用方式
     */
	@Test
	public void testCallStoredProcedure_v3(){
        // 上面的那些事务啥的,可以不要的,这只是一个查询的存储过程。
        
        // 获取 hibernate 的 session 对象
        Session session = entityManager.unwrap(Session.class);
        
        // 通过存储过程名 得到一个 ProcedureCall 对象
        ProcedureCall procedureCall = session.createStoredProcedureCall("A_TEST_SP_TWO");
        
        // 注册一个输入参数,ParameterMode.IN 表示输入参数,并通过bindValue()绑定值
        procedureCall.registerParameter("in_param_one", String.class, ParameterMode.IN).bindValue("%%");
        // 注册一个输出参数,ParameterMode.OUT 表示输入参数  写Class.class也行 或者 写Void.class也行
        procedureCall.registerParameter("out_param_one", Class.class, ParameterMode.REF_CURSOR);

        /*
         * 获取输出的结果。 如果配置了 oracle10G以下的方言是不支持通过存储过程来获取结果集的。
         * 
         * 在这个文章中引入的依赖是4.3.11.Final版本内置的Oracle方言最高就oracle10G,所以没办法,这里执行是报错的
         * 
         * 解决方法也有,升级Oracle,非要用Oracle10G也可以自定义方言,继承内置的那些方言的类然后做一下扩展。
         */
        Output output = procedureCall.getOutputs().getCurrent();
        List<Object[]> postComments = ( (ResultSetOutput) output ).getResultList();
        
        System.out.println(result);
	}
}

六、二级缓存

操作步骤:

  1. 创建工程

可以用eclipse创建JPA工程,new–>找到 JPA Project创建就行;也可以创建普通的java工程,也可用maven创建普通的java工程

  1. 加入依赖

maven的方式需要在上面Hello Word的例子引入的依赖的基础上加上下面的依赖

<!-- ehcache 用于二级缓存支持 -->
<dependency>
    <!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache-core -->
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.4.3</version>
</dependency>

<!-- hibernate-ehcache 用于二级缓存支持 -->
<dependency>
    <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-ehcache -->
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
    <version>4.2.4.Final</version>
</dependency>
  1. 编写persistence.xml文件

persistence.xml这个实际上是JPA最基本的配置文件,这里面会包含连接数据库的基本信息,会包含使用哪个ORM框架来作为JPA的基本实现,也会配置使用事务的方式。

注意:persistence.xml,文件的名称是固定的,放的文件夹也是固定的,他需要放在类路径下的META-INF 目录下。

<?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">
	<!-- 
		事务方式(transaction-type):
		    transaction-type  默认是JTA,JTA是可以使用分布式事务。 我们这里直接用本地事务就可以了 (transaction-type=RESOURCE_LOCAL)
		    
		    name  表示持久化单元的名字。   创建EntitymanagerFactory需要用到
	-->
	
	<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
		<!-- 
			配置使用什么 ORM 产品来作为 JPA 的实现 
			1. 实际上配置的是  javax.persistence.spi.PersistenceProvider 接口的实现类
			2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点. 
		-->
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		
		<!-- 添加持久化类 -->
		<class>com.xxx.jpademo.jpademo01.pojo.Customer</class>
		
		<!-- 
			配置二级缓存的策略 
			ALL:所有的实体类都被缓存
			NONE:所有的实体类都不被缓存. 
			ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
			DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类
			UNSPECIFIED:默认值,JPA 产品默认值将被使用

			<shared-cache-mode> 需要配置在<class>节点后面。 配置的值一般也都是配置 ENABLE_SELECTIVE
		-->
		<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
	
		<properties>
			<!-- 连接数据库的基本信息 -->
			<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
			<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa"/>
			<property name="javax.persistence.jdbc.user" value="root"/>
			<property name="javax.persistence.jdbc.password" value="root"/>
			
			<!-- 配置 JPA 实现产品的基本属性。   因为用hibernate作为JPA的实现,这里说白了就是配置 hibernate 的基本属性 -->
			<property name="hibernate.format_sql" value="true"/>
			<property name="hibernate.show_sql" value="true"/>
			<property name="hibernate.hbm2ddl.auto" value="update"/>
			
			
			<!-- 二级缓存相关 (实际也就是配置hibernate的二级缓存)-->
			<property name="hibernate.cache.use_second_level_cache" value="true"/>
			<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
			
			<!-- 这个是查询缓存 -->
			<property name="hibernate.cache.use_query_cache" value="true"/>
			
		</properties>
	</persistence-unit>
</persistence>

  1. 加入ehcache.xml文件,放到类路径下。缓存的配置这里做介绍,有兴趣去学习ehcache
<ehcache>

    <!-- Sets the path to the directory where cache .data files are created.

         If the path is a Java System Property it is replaced by
         its value in the running VM.

         The following properties are translated:
         user.home - User's home directory
         user.dir - User's current working directory
         java.io.tmpdir - Default temp file path -->
    <diskStore path="java.io.tmpdir"/>


    <!--Default Cache configuration. These will applied to caches programmatically created through
        the CacheManager.

        The following attributes are required for defaultCache:

        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

    <!--Predefined caches.  Add your cache configuration settings here.
        If you do not have a configuration for your cache a WARNING will be issued when the
        CacheManager starts

        The following attributes are required for defaultCache:

        name              - Sets the name of the cache. This is used to identify the cache. It must be unique.
        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->

    <!-- Sample cache named sampleCache1
        This cache contains a maximum in memory of 10000 elements, and will expire
        an element if it is idle for more than 5 minutes and lives for more than
        10 minutes.

        If there are more than 10000 elements it will overflow to the
        disk cache, which in this configuration will go to wherever java.io.tmp is
        defined on your system. On a standard Linux system this will be /tmp"
        -->
    <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

    <!-- Sample cache named sampleCache2
        This cache contains 1000 elements. Elements will always be held in memory.
        They are not expired. -->
    <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        />

    <!-- Place configuration for your caches following -->

</ehcache>

  1. 根据二级缓存的策略 在需要使用缓存的类上加 @Cacheable(true) 注解

import java.util.Date;

import javax.persistence.*;

@Cacheable(true) //根据需要加@Cacheable(true) 注解
@Table(name="jpa_cutomers")
@Entity
public class Customer {
	
	private Integer id;
	private String lastName;
	private String email;
	private int age;
	
	private Date birth;
	private Date createTime;
    
    // 省略getter、setter方法和一些映射注解的代码
}
  1. 测试
package com.xxx.jpademo.jpademo01;


import javax.persistence.*;

import org.junit.*;

import com.xxx.jpademo.jpademo01.pojo.Customer;


public class JPATest {

	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		//1. 创建 EntitymanagerFactory
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		//2. 创建 EntityManager. 
		entityManager = entityManagerFactory.createEntityManager();
		//3. 开启事务
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		//5. 提交事务
		transaction.commit();
		//6. 关闭 EntityManager
		entityManager.close();
		//7. 关闭 EntityManagerFactory
		entityManagerFactory.close();
	}
	
	@Test
	public void testSecondLevelCache(){
		Customer customer1 = entityManager.find(Customer.class, 1);
		
		transaction.commit();
		entityManager.close();
		
		entityManager = entityManagerFactory.createEntityManager();//不用二级缓存的情况下,关闭又开启,会发送两个sql
		transaction = entityManager.getTransaction();
		transaction.begin();
		
		Customer customer2 = entityManager.find(Customer.class, 1);
	}

}

使用 Hibernate 的查询缓存

/**
 * 使用 hibernate 的查询缓存. 
 *   前提是在配置文件中配置了启用查询缓存  <property name="hibernate.cache.use_query_cache" value="true"/>
 *   通过调用.setHint(QueryHints.HINT_CACHEABLE, true) 两句都要调用。告诉他要使用查询缓存
 * 
 */
@Test
public void testQueryCache(){
    String jpql = "FROM Customer c WHERE c.age > ?";
    Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);

    //占位符的索引是从 1 开始
    query.setParameter(1, 1);
    List<Customer> customers = query.getResultList();
    System.out.println(customers.size());

    query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);

    //占位符的索引是从 1 开始
    query.setParameter(1, 1);
    customers = query.getResultList();
    System.out.println(customers.size());
    
    //这样两个查询就只发一条语句
}

七、spring 整合 JPA

在和spring整合中有三种获取 EntityManagerFactory 的方式

  • LocalEntityManagerFactoryBean:适用于那些仅使用 JPA 进行数据访问的项目,该 FactoryBean 将根据JPA PersistenceProvider 自动检测配置文件进行工作,一般从“META-INF/persistence.xml”读取配置信息,这种方式最简单,但不能设置 Spring 中定义的 DataSource,且不支持 Spring 管理的全局事务

  • 从JNDI中获取:用于从 Java EE 服务器获取指定的EntityManagerFactory,这种方式在进行 Spring 事务管理时一般要使用 JTA 事务管理(不用javaee服务器这种方式也是用不了的)

  • LocalContainerEntityManagerFactoryBean:适用于所有环境的 FactoryBean,能全面控制 EntityManagerFactory 配置,如指定 Spring 定义的 DataSource 等等。(一般都用这个

引入依赖

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	
	<modelVersion>4.0.0</modelVersion>
	
	<groupId>com.xxx.jpademo</groupId>
	<artifactId>jpademo-spring-jpa</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	
	<packaging>jar</packaging>

    <name>jpademo15-spring-jpa</name>
    <url>http://maven.apache.org</url>

    <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
    
    	<!-- hibernate 依赖 。 在这个版本中 写了 hibernate-entitymanager 依赖,这个可以不要。-->
	  	 <dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.2.4.Final</version>
		</dependency>
		
		<!-- 要用jpa就要加这个依赖 -->
		<dependency>
			<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-entitymanager</artifactId>
		    <version>4.2.4.Final</version>
		</dependency>
    
    	<!-- 数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
			<scope>runtime</scope>
		</dependency>
		
		
		<!-- c3p0 -->
		<dependency>
			<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
		    <groupId>com.mchange</groupId>
		    <artifactId>c3p0</artifactId>
		    <version>0.9.2.1</version>
		</dependency>
		
			<!-- hibernate - c3p0 -->
		<dependency>
			<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-c3p0 -->
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-c3p0</artifactId>
		    <version>4.2.4.Final</version>
		</dependency>
  
  
    	<!-- spring相关依赖。    里面有一些spring重复依赖的排除其实是可以不写的,因为maven有一个机制让相同的覆盖掉 -->
        <dependency>
			<!-- 用spring ioc的相关功能,引入这个就够了的。它里面多了个spring-aop但这没关系 -->
	        <groupId>org.springframework</groupId>
	        <artifactId>spring-context</artifactId>
			<!-- spring 4.0.0.RELEASE 这个版本中 spring-context这个依赖包含了spring-aop、spring-beans、spring-core、spring-expression,当然也就包含了这里面的一些层级相关依赖 -->
			<!-- spring-aop包含 spring-beans,spring-core,aopalliance -->
			<!-- spring-beans包含了spring-core -->
			<!-- spring-core包含了commons-logging -->
			<!-- spring-expression包含了spring-core -->
			<version>4.0.0.RELEASE</version>
		</dependency>
		
		<!-- spring aop相关功能的依赖 -->
		<dependency>
		   <groupId>org.springframework</groupId>
		   <artifactId>spring-aspects</artifactId>
			<!-- spring 4.0.0.RELEASE 这个版本中  spring-aspects这个依赖包含了aspectjweaver-1.8.0.M1、spring-context-support -->
			<!-- spring-context-support 有没有这个依赖也没关系。 -->
		   <version>4.0.0.RELEASE</version>
		   <exclusions>
		      <exclusion>
		        <!-- 配置了阿里云的仓库。然后 spring 4.0.0.RELEASE 这个版本中 下载不到这个 aspectjweaver 依赖。这里把它排除掉。下载到了应该也没关系 -->
		        <groupId>org.aspectj</groupId>
				<artifactId>aspectjweaver</artifactId> 
		      </exclusion>
		   </exclusions>
		</dependency>
		
		<dependency>
		    <!-- 对象关系映射,集成orm框架, -->
		   <groupId>org.springframework</groupId>
		   <artifactId>spring-orm</artifactId>
		    <!--  spring 4.0.0.RELEASE 这个版本中 spring-jdbc这个依赖包含了aopalliance、spring-beans、spring-core、spring-tx、spring-jdbc 当然也就包含了这里面的一些层级相关依赖 -->
		   <version>4.0.0.RELEASE</version>
		</dependency>
		
		<dependency>
		    <!--  -->
		   <groupId>org.springframework</groupId>
		   <artifactId>spring-webmvc</artifactId>
		    <!--  spring 4.0.0.RELEASE 这个版本中 spring-web这个依赖包含了spring-beans、spring-context、spring-core、spring-expression、spring-web 当然也就包含了这里面的一些层级相关依赖 -->
		   <version>4.0.0.RELEASE</version>
		</dependency>
		
		
		<!-- 
		    这些是跟上面spring配套使用的,用于AOP方面的。但是从maven的仓库下载不到。只能手动的将他安装到maven的本地仓库 
		    
		    这些jar包下载不到。可以参考下面的maven命令手动安装到自己的Maven仓库

			mvn install:install-file "-Dfile=D:\jar包所在位置\xxxx.jar" "-DgroupId=org.aspectj" "-DartifactId=com.springsource.org.aspectj.weaver" "-Dversion=1.6.8.RELEASE" "-Dpackaging=jar"
		 -->
		
		<dependency>
		<!-- https://mvnrepository.com/artifact/net.sourceforge.cglib/com.springsource.net.sf.cglib -->
		    <groupId>net.sourceforge.cglib</groupId>
		    <artifactId>com.springsource.net.sf.cglib</artifactId>
		    <version>2.2.0</version>
		</dependency>
		
		<dependency>
		<!-- https://mvnrepository.com/artifact/org.aopalliance/com.springsource.org.aopalliance -->
		    <groupId>org.aopalliance</groupId>
		    <artifactId>com.springsource.org.aopalliance</artifactId>
		    <version>1.0.0</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.aspectj/com.springsource.org.aspectj.weaver -->
		<dependency>
		    <groupId>org.aspectj</groupId>
		    <artifactId>com.springsource.org.aspectj.weaver</artifactId>
		    <version>1.6.8.RELEASE</version>
		</dependency>
		
		
    	<dependency>
	      <groupId>junit</groupId>
	      <artifactId>junit</artifactId>
	      <version>3.8.1</version>
	      <scope>test</scope>
	    </dependency>
    
    </dependencies>
    
    
    <build>
  	  <plugins>  
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>  
          <artifactId>maven-compiler-plugin</artifactId>  
          <configuration>  
            <source>1.8</source>  
            <target>1.8</target>  
          </configuration>  
        </plugin>  
      </plugins> 
    </build>
    
</project>

在这里插入图片描述

在类路径下编写spring的配置文件 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

	<!-- 配置自动扫描的包 -->
	<context:component-scan base-package="com.xxx.jpademo.spring"></context:component-scan>

	<!-- 配置 C3P0 数据源 -->
	<context:property-placeholder location="classpath:db.properties"/>

	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${jdbc.user}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<property name="driverClass" value="${jdbc.driverClass}"></property>
		<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>	
		
		<!-- 配置其他属性 -->
	</bean>
	
	<!-- 配置 EntityManagerFactory -->
	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	
		<!-- 配置dataSource -->
		<property name="dataSource" ref="dataSource"></property>
		
		<!-- 配置 JPA 提供商的适配器. 可以通过内部 bean 的方式来配置 -->
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
		</property>	
		
		<!-- 配置实体类(Entity)所在的包 -->
		<property name="packagesToScan" value="com.xxx.jpademo.spring.entityes"></property>
		
		<!-- 配置 JPA 的基本属性. 例如 JPA 实现产品的属性。(在这个例子里说白了就是配 hibernate 的那些 show_sql 之类的属性) -->
		<property name="jpaProperties">
			<props>
				<!-- 二级缓存相关 -->
				<!-- <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop> -->
				<!-- <prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop> -->
				<!-- 生成的数据表的列的映射策略 -->
				<!-- <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> -->
			
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.format_sql">true</prop>
				<prop key="hibernate.hbm2ddl.auto">update</prop>
			</props>
		</property>
		
	</bean>
	
	<!-- 配置 JPA 使用的事务管理器 -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory"></property>	
	</bean>
	
	<!-- 配置支持基于注解是事务配置 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

记得编写一个db.properties

jdbc.user=root
jdbc.password=root
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/jpa

#MySql 8 的写法如下
#jdbc.user=root
#jdbc.password=root
#jdbc.driverClass=com.mysql.cj.jdbc.Driver
#jdbc.jdbcUrl=jdbc:mysql://localhost:3306/pmtest?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8

写一个实体类 Person

package com.xxx.jpademo.spring.entityes;

import javax.persistence.*;

@Table(name="jpa_persons")
@Entity
public class Person {

	private Integer id;
	private String lastName;

	private String email;
	private int age;

	@GeneratedValue
	@Id
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name="last_name")
	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

写一个DAO

package com.xxx.jpademo.spring.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import com.xxx.jpademo.spring.entityes.Person;

@Repository
public class PersonDao {
	
	//如何获取到和当前事务关联的 EntityManager 对象呢 ?
	//通过 @PersistenceContext 注解来标记成员变量!
	@PersistenceContext
	private EntityManager entityManager;
	
	/**
	 * 用这个都是创建新的,不靠谱!  如何获取到和当前事务关联的 EntityManager 对象呢 ?---->通过 @PersistenceContext 注解来标记成员变量!
	 * 如果从spring中注入 EntityManagerFactory 然后通过它的 createEntityManager 方法获取 EntityManager 都是创建新的 EntityManager 得不
	 * 到和当前事务绑定的 EntityManager 是不靠谱的 
	 * 
	 * @Autowired
	 * private EntityManagerFactory entityManagerFactory;
	 */
	
	public void save(Person person){
		
		/**
		 * 用这个都是创建新的,不靠谱!  如何获取到和当前事务关联的 EntityManager 对象呢 ?---->通过 @PersistenceContext 注解来标记成员变量!
		 * entityManagerFactory.createEntityManager()
		 */
		entityManager.persist(person);
	}
}

编写一个service

package com.xxx.jpademo.spring.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.xxx.jpademo.spring.dao.PersonDao;
import com.xxx.jpademo.spring.entityes.Person;

@Service
public class PersonService {
	
	@Autowired
	private PersonDao personDao;
	
	@Transactional
	public void savePersons(Person p1, Person p2){
		personDao.save(p1);
		//测试报异常是否能保存,有了 @Transactional 注解正常情况下是不能保持其中的任何一个 Person 的
		int i = 10 / 0;
		
		personDao.save(p2);
	}
}

测试

package com.xxx.jpademo.jpademo01;

import java.sql.SQLException;
import javax.sql.DataSource;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.xxx.jpademo.spring.entityes.Person;
import com.xxx.jpademo.spring.service.PersonService;

public class JPATest {
	
	private ApplicationContext ctx = null;
	private PersonService personService = null;
	
	{
		ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		personService = ctx.getBean(PersonService.class);
	}
	
	@Test
	public void testDataSource() throws SQLException {
		//测试spring是否能成功使用
		DataSource dataSource = ctx.getBean(DataSource.class);
		System.out.println(dataSource.getConnection());
	}
	
	
	@Test
	public void testPersonService(){
		//测试spring jpa 保存
		Person p1 = new Person();
		p1.setAge(11);
		p1.setEmail("aa@163.com");
		p1.setLastName("AA");
		
		Person p2 = new Person();
		p2.setAge(12);
		p2.setEmail("bb@163.com");
		p2.setLastName("BB");
		
		System.out.println(personService.getClass().getName());// 如果这里打印出一个代理对象的话说明这个事务是被加上了
		personService.savePersons(p1, p2);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值