SSH Chapter 13 Spring Data JPA 笔记
本章目标:
1 . JPA(Java Persistence API):
1.1 JPA的概述:
JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。
JPA通过注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中,是一套Sun公司Java官方制定的ORM 方案,是规范,是标准 ,sun公司自己并没有实现
Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。
1.2 JPA的优势
-
标准化 : JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
-
容器级特性的支持 : JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
-
简单方便 : JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成
-
查询能力 : JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。
JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。 -
高级特性 : JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
1.3 JPA的作用是什么:
JPA是ORM的一套标准,既然JPA为ORM而生,那么JPA的作用就是实现使用对象操作数据库,不用写SQL!!!
问题:数据库是用sql操作的,那用对象操作,由谁来产生SQL?
答:JPA实现框架
1.4 JPA与Hibernate的关系:
既然我们说JPA是一套标准,意味着,它只是一套实现ORM理论的接口。没有实现的代码。 它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服务厂商来提供实现。
市场上的主流的JPA框架(实现者)有:
Hibernate (JBoos)、EclipseTop(Eclipse社区)、OpenJPA (Apache基金会)。
其中Hibernate是众多实现者之中,性能最好的。所以,我们本次也是选用Hibernate框架作为JPA的入门框架。
JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。也就是说,如果使用JPA规范进行数据库操作,底层需要Hibernate作为其实现类完成数据持久化工作。
提示:学习一个JPA框架,其他的框架都是一样使用
1.5 JPA的入门示例
1. 需求介绍
本章节我们是实现的功能是保存一个产品到数据库的产品表中。
2. 开发包介绍
由于JPA是sun公司制定的API规范,所以我们不需要导入额外的JPA相关的jar包,只需要导入JPA的提供商的jar包。我们选择Hibernate作为JPA的提供商,所以需要导入Hibernate的相关jar包。
下载网址:
http://sourceforge.net/projects/hibernate/files/hibernate-orm/5.4.2.Final/
3. 搭建开发环境
3.1 导入jar包
maven工程导入坐标如下:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--统一定义maven的版本号-->
<hibernate.version>5.4.2.Final</hibernate.version>
</properties>
<dependencies>
<!--使用hibernate-core 或者加入hibernate-c3p0以及hibernate-entitymanager-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- hibernate-c3p0 -->
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${hibernate.version}</version>
</dependency>-->
<!-- hibernate-entitymanager -->
<!--<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>-->
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--slf4j 默认使用slf4j记录日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
3.2 创建数据表和实体类:
创建产品表并插入对应的数据:
create table product
(
product_id int auto_increment
primary key,
product_name varchar(20) not null
);
/**
|| 插入表的语句如下:
*/
insert into product values(null,'笔记本');
insert into product values(null,'手机');
创建实体类,并使用JPA注解的形式配置映射关系:
@Entity
@Table(name = "product")
public class Product implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "product_id")
private Integer productId;
@Column(name = "product_name")
private String productName;
//省略getter setter 以及 toString()
}
常用注解的说明(与Hibernate中关于实体类的注解作用一致)如下:
@Entity
作用:指定当前类是实体类。
@Table
作用:指定实体类和表之间的对应关系。
属性:
name:指定数据库表的名称
@Id
作用:指定当前字段是主键。
@GeneratedValue
作用:指定主键的生成方式。。
属性:
strategy :指定主键生成策略(可参考SSH_CH07笔记)。
@Column
作用:指定实体类属性和数据库表之间的对应关系
属性:
name:指定数据库表的列名称。
unique:是否唯一
nullable:是否可以为空
inserttable:是否可以插入
updateable:是否可以更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),
该属性定义该列所在从表的名字搭建开发环境
3.3 创建JPA的核心配置文件:
在 Java 工程的resources目录下创建一个名为META-INF的文件夹 , 在此文件夹下创建一个名为 persistence.xml 的配置文件 , 内容可参考:hibernate-release-5.4.2.Final\documentation\quickstart\html_single\entitymanager\src\test\resources\ -INF
下的persistence.xml
文件
代码如下:
<persistence 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"
version="2.0">
<!--配置持久化单元
name:持久化单元名称
transaction-type:事务类型
RESOURCE_LOCAL:本地事务管理
JTA:分布式事务管理 -->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<description>
JPA入门案例演示
</description>
<!--持久化类-->
<class>cn.smbms.pojo.Product</class>
<properties>
<!-- 数据库驱动 -->
<property name="javax.persistence.jdbc.driver"
value="com.mysql.jdbc.Driver" />
<!-- 数据库地址 -->
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf-8&useSSL=false" />
<!-- 数据库用户名 -->
<property name="javax.persistence.jdbc.user" value="root" />
<!-- 数据库密码 -->
<property name="javax.persistence.jdbc.password" value="root" />
<!--jpa提供者的可选配置:我们的JPA规范的提供者为hibernate,
所以jpa的核心配置中兼容hibernate的配 -->
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true"/>
<!--hibernate.hbm2ddl.auto参数的作用主要用于:自动创建|更新|验证数据库表结构。
如果不是此方面的需求建议set value="none"-->
<property name="hibernate.hbm2ddl.auto" value="none" />
</properties>
</persistence-unit>
</persistence>
如果使用log4j记录日志时 , 需要在pom.xml
文件中加入以下依赖:
<!--启用log4j2的日志依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
然后在resources目录下创建log4j.properties , 内容如下:
#
# Hibernate, Relational Persistence for Idiomatic Java
#
# License: GNU Lesser General Public License (LGPL), version 2.1 or later.
# See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
#
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=warn, stdout
#log4j.logger.org.hibernate=info
log4j.logger.org.hibernate=debug
### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug
### log just the SQL
#log4j.logger.org.hibernate.SQL=debug
### log JDBC bind parameters ###
log4j.logger.org.hibernate.type=info
#log4j.logger.org.hibernate.type=debug
### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=debug
### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug
### log cache activity ###
#log4j.logger.org.hibernate.cache=debug
### log transaction activity
#log4j.logger.org.hibernate.transaction=debug
### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug
### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
可参考hibernate-release-5.4.2.Final\project\etc
目录下的log4j.properties
文件 , 如图所示:
3.4 实现持久化操作
创建测试类 , 并编写测试方法 , 代码如下:
@Test
public void testSave(){
/**
* 创建实体管理类工厂,借助Persistence的静态方法获取
* 其中传递的参数为持久化单元名称,需要jpa配置文件中指定
*/
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory( "myJpa" );
// 创建实体管理类
EntityManager entityManager = entityManagerFactory.createEntityManager();
//创建事务对象并开启事务
entityManager.getTransaction().begin();
//保存操作
Product product = new Product();
product.setProductName("平板");
entityManager.persist(product);
//提交事务
entityManager.getTransaction().commit();
//释放资源
entityManager.close();
entityManagerFactory.close();;
}
测试类可参考hibernate-release-5.4.2.Final\documentation\quickstart\html_single\entitymanager\src\test\java\org\hibernate\tutorial\em
目录下的EntityManagerIllustrationTest.java
文件 , 如图所示:
测试类中的查询方法如下:
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Test
public void testGetList(){
/**
* 创建实体管理类工厂,借助Persistence的静态方法获取
* 其中传递的参数为持久化单元名称,需要jpa配置文件中指定
*/
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory( "myJpa" );
// 创建实体管理类
EntityManager entityManager = entityManagerFactory.createEntityManager();
//创建事务对象并开启事务
entityManager.getTransaction().begin();
//查询操作
List<Product> products = entityManager.createQuery("from Product", Product.class).getResultList();
products.forEach(p->logger.info("product:{}",p));
//提交事务
entityManager.getTransaction().commit();
//释放资源
entityManager.close();
entityManagerFactory.close();;
}
4. JPA的API介绍
4.1 JPA的API介绍
Persistence对象主要作用是用于获取EntityManagerFactory对象的 。通过调用该类的createEntityManagerFactory静态方法,根据配置文件中持久化单元名称创建EntityManagerFactory。
//1. 创建 EntitymanagerFactory
String unitName = "myJpa";
EntityManagerFactory factory= Persistence.createEntityManagerFactory(unitName);
4.2 EntityManagerFactory
EntityManagerFactory 接口主要用来创建 EntityManager 实例
//创建实体管理类
EntityManager em = factory.createEntityManager();
由于EntityManagerFactory是一个线程安全的对象(即多个线程访问同一个EntityManagerFactory 对象不会有线程安全问题),并且EntityManagerFactory 的创建极其浪费资源,所以在使用JPA编程时,我们可以对EntityManagerFactory的创建进行优化,只需要做到一个工程只存在一个EntityManagerFactory 即可
4.3 EntityManager
在 JPA 规范中, EntityManager是完成持久化操作的核心对象。实体类作为普通 java对象,只有在调用 EntityManager将其持久化后才会变成持久化对象。
EntityManager对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。
我们可以通过调用EntityManager的方法完成获取事务,以及持久化数据库的操作
方法说明:
getTransaction : 获取事务对象
persist : 保存操作
merge : 更新操作
remove : 删除操作
find/getReference : 根据id查询
4.4 EntityTransaction
在 JPA 规范中, EntityTransaction是完成事务操作的核心对象,对于EntityTransaction在我们的java代码中承接的功能比较简单
begin:开启事务
commit:提交事务
rollback:回滚事务
5. 封装JPAUtil工具类
封装JPAUtil工具类 , 代码如下:
public class JPAUtil {
private static EntityManagerFactory emf;
static {
emf = Persistence.createEntityManagerFactory( "myJpa" );
}
public static EntityManager createEntityManager(){
return emf.createEntityManager();
}
public static void rollback(EntityTransaction et){
if (et != null) {
et.rollback();
}
}
public static void close(EntityManager em){
if (em!=null){
em.close();
}
}
}
6. 使用JPA完成增删改查操作
1. 保存
@Test
public void testAdd(){
// 创建实体管理类
EntityManager em = null;
EntityTransaction tx = null;
try {
em = JPAUtil.createEntityManager();
tx = em.getTransaction();
tx.begin();
Product product = new Product();
product.setProductName("空调");
em.persist(product);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
JPAUtil.rollback(tx);
}finally {
JPAUtil.close(em);
}
}
2. 修改
@Test
public void testMerge(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
Product product = em.find(Product.class, 4);
product.setProductName("电视");
//或者调用merge方法
//em.merge(product);
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
3. 删除
@Test
public void testRemove(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
Product product = em.find(Product.class, 4);
em.remove(product);
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
4. 查询
测试立即加载一个对象
/**
* 测试立即加载
*/
@Test
public void testGetOne(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
Product product = em.find(Product.class,3);
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
测试实体中的缓存问题:
/**
* 测试实体类中的缓存问题
*/
@Test
public void testGetOne(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
Product product1 = em.find(Product.class,3);
Product product2= em.find(Product.class,3);
System.out.println(product1 == product2);//true
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
测试延迟查询一个对象:
/**
* 测试延迟加载
*/
@Test
public void testLoadOne(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
Product product = em.getReference(Product.class,3);
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
7. JPA中的复杂查询
JPQL全称Java Persistence Query Language
基于首次在EJB2.0中引入的EJB查询语言(EJB QL),Java持久化查询语言(JPQL)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起。使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。
其特征与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性。
1. 查询全部:
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 查询所有的产品信息
*/
@Test
public void testFindAll(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
String jpql = "from Product";
TypedQuery<Product> query = em.createQuery(jpql, Product.class);
List<Product> products = query.getResultList();
products.forEach(p->logger.info("product:{}",p));
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
2. 分页查询:
/**
* 分页查询所有的产品信息
*/
@Test
public void testFindAllByPage(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
String jpql = "from Product";
TypedQuery<Product> query = em.createQuery(jpql, Product.class);
int pageNo = 1;//当前页
int pageSize = 2;//每页显示的记录数
//查询总记录
int count = em.createQuery("select count(*) from " +
"Product ",Long.class).getSingleResult().intValue();
//计算总页数
int totalPage = count%pageSize == 0 ? count/pageSize:
count/pageSize+1;
System.out.println("总记录数是:"+count);
System.out.println("总页数是:"+totalPage);
//起始索引 (pageNo-1)*pageSize
query.setFirstResult((pageNo-1)*pageSize);
//每页显示的条数
query.setMaxResults(pageSize);
List<Product> products = query.getResultList();
products.forEach(p->logger.info("product:{}",p));
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
3. 条件查询:
/**
* 按照条件查询
*/
@Test
public void testFindCondition(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
//模糊查询(使用参数位置绑定)
String jpql = "from Product where productName like concat('%',?1,'%')";
//或者使用参数名称绑定传递:
//String jpql = "from Product where productName like concat('%',:productName,'%')";
TypedQuery<Product> query = em.createQuery(jpql, Product.class);
//模糊查询(使用占位符)
query.setParameter(1,"平");
//模糊查询(使用参数传递)
//query.setParameter("productName","平");
List<Product> products = query.getResultList();
products.forEach(p->logger.info("product:{}",p));
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
4. 统计查询
/**
* 统计查询
*/
@Test
public void testFindCount(){
// 定义对象
EntityManager em = null;
EntityTransaction tx = null;
try {
//获取实体管理对象
em = JPAUtil.createEntityManager();
//获取事务对象
tx = em.getTransaction();
//开启事务
tx.begin();
//模糊查询
String jpql = "select count(*) from Product ";
Query query = em.createQuery(jpql);
Object count = query.getSingleResult();
logger.info("count: {}",count);
//提交事务
tx.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
JPAUtil.rollback(tx);
}finally {
//释放资源
JPAUtil.close(em);
}
}
2. Spring Data JPA
2.1 Spring Data JPA 概述
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦
2.2 Spring Data JPA 的特性:
SpringData Jpa 极大简化了数据库访问层代码。 如何简化的呢? 使用了Spring Data JPA,我们的dao层中只需要写接口,就自动具有了增删改查、分页查询等方法
2.3 Spring Data JPA 与 JPA 和 Hibernate之间的关系
JPA是一套规范,内部是由接口和抽象类组成的。Hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称Hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)
Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。
3. Spring Data JPA的快速入门
3.1 需求说明:
Spring Data JPA完成产品的基本CRUD操作
3.2 搭建Spring Data JPA的开发环境
1. 引入Spring Data JPA相关的jar包
使用Spring Data JPA,需要整合Spring与Spring Data JPA,并且需要提供JPA的服务提供者Hibernate,所以需要导入Spring相关坐标,Hibernate坐标,数据库驱动坐标等
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--统一定义maven的版本号-->
<hibernate.version>5.4.10.Final</hibernate.version>
<!--省略其他依赖版本的定义-->
</properties>
<dependencies>
<!--spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<!-- hibernate-c3p0 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- hibernate-entitymanager -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!--hibernate-jpa-2.0-api有bug-->
<!-- hibernate-jpa-2.1-api -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.2.Final</version>
</dependency>
<!--junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
2. 整合Spring Data JPA与Spring
在工程resources目录下创建applicationContext.xml,内容如下:
<!--省略头部文件-->
<!--扫描c3p0.properties文件-->
<context:property-placeholder location="classpath:c3p0.properties"/>
<!--c3p0数据源-->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl"
value="${c3p0.jdbcUrl}"/>
<property name="driverClass" value="${c3p0.driverClass}"/>
<property name="user" value="${c3p0.user}"/>
<property name="password" value="${c3p0.password}"/>
</bean>
<!--配置entityManagerFactory-->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!--数据源-->
<property name="dataSource" ref="dataSource"/>
<!--实体类注解扫描的包名-->
<property name="packagesToScan" value="cn.smbms.pojo"/>
<!--jpa实现厂家 可以不用配置-->
<!--<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>-->
<!--供应商适配器-->
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false"/>
<property name="database" value="MYSQL"/>
<!--<property name="databasePlatform"
value="org.hibernate.dialect.MySQLDialect"/>-->
<property name="showSql" value="true"/>
</bean>
</property>
<!--jpa方言 也可以不用配置-->
<!--<property name="jpaDialect">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>-->
</bean>
<!--配置jpa事务管理器-->
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--定义事务管理规则-->
<tx:advice id="transactionInterceptor"
transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*"/>
<tx:method name="insert*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut"
expression="execution(* cn.smbms.service..*.*(..))"/>
<!--将事务管理规则应用于切入点-->
<aop:advisor advice-ref="transactionInterceptor"
pointcut-ref="pointcut"/>
</aop:config>
<!--Spring整合data jpa-->
<jpa:repositories base-package="cn.smbms.dao" transaction-manager-ref="txManager"
entity-manager-factory-ref="entityManagerFactory"/>
<!--Spring的注解扫描-->
<context:component-scan base-package="cn.smbms"/>
注意:配置JPA事务,使用的事务管理器是JpaTransactionManager
此配置内容可参考官网案例:https://github.com/spring-projects/spring-data-book/blob/master/jpa/src/main/resources/META-INF/spring/application-context.xml
c3p0.properties文件内容如下:
c3p0.jdbcUrl=jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf-8&useSSL=false
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.user=root
c3p0.password=root
log4j.properties文件内容如下:
#
# Hibernate, Relational Persistence for Idiomatic Java
#
# License: GNU Lesser General Public License (LGPL), version 2.1 or later.
# See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
#
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=warn, stdout
#log4j.logger.org.hibernate=info
log4j.logger.org.hibernate=debug
### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug
### log just the SQL
#log4j.logger.org.hibernate.SQL=debug
### log JDBC bind parameters ###
log4j.logger.org.hibernate.type=info
#log4j.logger.org.hibernate.type=debug
### log schema export/update ###
log4j.logger.org.hibernate.tool.hbm2ddl=debug
### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug
### log cache activity ###
#log4j.logger.org.hibernate.cache=debug
### log transaction activity
#log4j.logger.org.hibernate.transaction=debug
### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug
### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
3. 使用JPA注解配置映射关系
实体类与表之间的映射关系 , 可参考上个案例 , 内容如下所示:
@Entity
@Table(name = "product")
//@Proxy(lazy = false)//启用立即加载
public class Product implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "product_id")
private Integer productId;
@Column(name = "product_name")
private String productName;
//省略getter setter 以及 toString()
}
3.3 使用Spring Data JPA完成需求:
1. 编写符合Spring Data JPA 规范的DAO接口
Spring Data JPA是spring提供的一款对于数据访问层(Dao层)的框架,使用Spring Data JPA,只需要按照框架的规范提供dao接口,不需要实现类就可以完成数据库的增删改查、分页查询等方法的定义,极大的简化了我们的开发过程。
在Spring Data JPA中,对于定义符合规范的Dao层接口,我们只需要遵循以下几点就可以了:
- 创建一个Dao层接口,并继承JpaRepository和JpaSpecificationExecutor
- 提供相应的泛型
代码如下:
/**
* JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
* JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
*/
public interface ProductDao extends JpaRepository<Product,Integer>,
JpaSpecificationExecutor<Product> {
}
这样我们就定义好了一个符合Spring Data JPA规范的Dao层接口
2. 完成基本CRUD操作
完成了Spring Data JPA的环境搭建,并且编写了符合Spring Data JPA 规范的Dao层接口之后,就可以使用定义好的Dao层接口进行客户的基本CRUD操作
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 测试增加操作
*/
@Test
public void testSave(){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
Product product = new Product();
product.setProductName("空调");
//插入数据 并返回一个持久化对象(包含了主键id)
product = context.getBean(ProductDao.class).save(product);
log.info("product.productId=>{}",product.getProductId());
}
/**
* 测试删除操作
*/
@Test
public void testDelete(){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
context.getBean(ProductDao.class).deleteById(6);
}
/*修改商品:调用save(obj)方法
* 对于save方法的解释:如果执行此方法是对象中存在id属性,
* 即为更新操作会先根据id查询,再更新
* 如果执行此方法中对象中不存在id属性,即为保存操作
*/
@Test
public void testUpdate(){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
//getOne是延迟加载 find是立即加载
Product product = new Product();
product.setProductId(5);
product.setProductName("空调");
//主键存在时 根据主键先查询 然后再进行修改
context.getBean(ProductDao.class).save(product);
}
/**
* 根据ID查询单个商品: findById
*/
@Test
public void testFindOne(){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
ProductDao productDao = context.getBean(ProductDao.class);
Optional<Product> optional = productDao.findById(5);
if (optional.isPresent()){
Product p = optional.get();
log.info("p:{}",p);
}
}
/**
* 查询所有商品信息的方法:findAll
*/
@Test
public void testFindAll(){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
context.getBean(ProductDao.class).findAll().forEach(p->log.info(
"product:{}",p));
}
备注:
至于Spring Data JPA更多的API,可参考官网:https://docs.spring.io/spring-data/jpa/docs/2.2.6.RELEASE/api/
4. Spring Data JPA的内部原理剖析
4.1 Spring Data JPA的常用接口分析
在商品的案例中,我们发现在自定义的ProductDao中,并没有提供任何方法就可以使用其中的很多方法,那么这些方法究竟是怎么来的呢?答案很简单,对于我们自定义的Dao接口,由于继承了JpaRepository和JpaSpecificationExecutor,所以我们可以使用这两个接口的所有方法。
在使用Spring Data JPA时,一般实现JpaRepository和JpaSpecificationExecutor接口,这样就可以使用这些接口中定义的方法,但是这些方法都只是一些声明,没有具体的实现方式,那么在 Spring Data JPA中它又是怎么实现的呢?
4.2 Spring Data JPA的实现过程
通过对商品案例的测试,以debug断点调试的方式,分析Spring Data JPA的执行过程
我们以findById方法为例进行分析
代理子类的实现过程:
断点执行到方法上时,我们可以发现通过容器获取到的productDao对象,本质上是通过JdkDynamicAopProxy生成的一个代理对象
代理对象中方法调用的分析
当程序执行的时候,会通过JdkDynamicAopProxy的invoke方法,对productDao对象生成动态代理对象。
根据对Spring Data JPA介绍而知,要想进行findById查询方法,最终还是会出现JPA规范的API完成操作,那么这些底层代码存在于何处呢?答案很简单,都隐藏在通过JdkDynamicAopProxy生成的动态代理对象当中,而这个动态代理对象就是SimpleJpaRepository
通过SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?
带着问题继续查找em对象,我们发现em就是EntityManager对象,而他是JPA原生的实现方式,所以我们得到结论Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发
4.3 Spring Data JPA完整的调用过程分析:
5. Spring Data JPA的查询方式
5.1 使用Spring Data JPA中定义的方法进行查询
在继承JpaRepository,和JpaRepository接口后,我们就可以使用接口中定义的方法进行查询
继承JpaRepository后的方法列表
继承JpaSpecificationExecutor的方法列表
5.2 Sort与Pageable:
Sort:
Sort对象使用来排序的,默认采用升序排列。
案例如下:
@Test
public void testFindBySort(){
//对产品列表使用id进行降序排列 并且使用name进行升序排列
Sort sort = Sort.by("productId").descending().and(Sort.by(Sort.Direction.ASC,"productName"));
productDao.findAll(sort).forEach(System.out::println);
}
Pageable:
Pageable接口用于构造分页查询,PageRequest是其实现类。
案例如下:
@Test
public void testFindByPage(){
//page当前页码,从0开始,size指每页显示的记录数
Pageable pageable = PageRequest.of(0,2);
//可以使用Page类型的 变量接收返回值
//通过productPage可以获取有关分页的一系列信息 以及泛型集合
Page<Product> productPage = productDao.findAll(pageable);
//总记录数
long totalElements = productPage.getTotalElements();
System.out.println(totalElements);
//总页码
int totalPages = productPage.getTotalPages();
System.out.println(totalPages);
//每页显示的条数
int size = productPage.getSize();
System.out.println(size);
//数据集合
List<Product> productList = productPage.getContent();
productList.forEach(System.out::println);
}
将分页与排序合二为一,演示如下:
@Test
public void testFindBySortAndPage(){
Sort sort = Sort.by("productId").descending().and(Sort.by("productName").ascending());
//page当前页码,从0开始,size指每页显示的记录数
Pageable pageable = PageRequest.of(0,2,sort);
//可以使用Page类型的 变量接收返回值
//通过productPage可以获取有关分页的一系列信息 以及产品集合
Page<Product> productPage = productDao.findAll(pageable);
//总记录数
long totalElements = productPage.getTotalElements();
System.out.println(totalElements);
//总页码
int totalPages = productPage.getTotalPages();
System.out.println(totalPages);
//每页显示的条数
int size = productPage.getSize();
System.out.println(size);
//数据集合
List<Product> productList = productPage.getContent();
productList.forEach(System.out::println);
}
5.3 使用JPQL的方式查询:
使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询
@Query
注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可
/**
* JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
* JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
*/
public interface ProductDao extends JpaRepository<Product,Integer>,
JpaSpecificationExecutor<Product> {
//@Query 使用jpql的方式查询。
@Query("from Product")
List<Product> findAllProduct();
//按照商品名模糊查询
@Query("from Product where productName like concat('%',?1,'%') ")
List<Product> findProduct(String productName);
//按照id查询商品信息
@Query("from Product where productId =:id")
Product findProductId(@Param("id") Integer id);
}
此外,也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询
//使用Query和Modifying更新商品信息
@Query(value="update Product set productName=:name where productId=:id")
@Modifying//注意测试此方法 必须有事务支持 否则无法修改成功
void updateProduct(@Param("id") int id,@Param("name") String name);
5.4 使用SQL语句查询:
Spring Data JPA同样也支持sql语句的查询,如下:
/**
* nativeQuery:是否启用本地sql的方式查询(true为是,false为否,默认值为false)
* @return
*/
//注意此处的SQL语句必须写表名 以及 列名
@Query(value = "select * from PRODUCT",nativeQuery = true)
List<Product> findSql();
在Spring Data JPA 1.4以后,支持在@Query
中使用SpEL表达式
(简介)来接收变量。
SpEL支持的变量:
变量名 | 使用方式 | 描述 |
---|---|---|
entityName | select x from #{#entityName} x | 根据给定的Repository自动插入相关的entityName。有两种方式能被解析出来:如果域类型定义了@Entity属性名称。或者直接使用类名称。 |
@Query("select p from #{#entityName} p where p.productId=?1")
Product selectProductById(@Param("id") int id);
5.5 Example动态查询:
Example官方介绍:
按例查询(QBE)是一种用户界面友好的查询技术。 它允许动态创建查询,并且不需要编写包含字段名称的查询。 实际上,按示例查询不需要使用特定的数据库的查询语言来编写查询语句。
优点:
- 可以使用动态或者静态的限制去查询
- 在重构你的实体的时候,不用担心影响到已有的查询
- 可以独立地工作在数据查询API之外
缺点:
- 不支持组合查询,比如:firstname = ?0 or (firstname = ?1 and lastname = ?2).
- 灵活匹配只支持字符串类型,其他类型只支持精确匹配
Example API的组成:
- Probe: 含有对应字段的实例对象。
- ExampleMatcher:ExampleMatcher携带有关如何匹配特定字段的详细信息,相当于匹配条件。
- Example:由Probe和ExampleMatcher组成,用于查询。
案例如下:
测试查询:
//封装实体类对象 根据id查询产品信息
@Test
public void testFindProductById(){
Product p = new Product();
p.setProductId(1);
Example<Product> example = Example.of(p);
productDao.findAll(example).forEach(System.out::println);
}
可以发现,试用Example查询,默认情况下会忽略空值,官方文档也有说明!点击查看。
在上面的测试之中,我们只是只是定义了Probe而没有ExampleMatcher,是因为默认会不传时会使用默认的匹配器。
源码如下:
static <T> Example<T> of(T probe) {
return new TypedExample(probe, ExampleMatcher.matching());
}
static ExampleMatcher matching() {
return matchingAll();
}
static ExampleMatcher matchingAll() {
return (new TypedExampleMatcher()).withMode(ExampleMatcher.MatchMode.ALL);
}
自定义匹配器规则:
//按照产品名模糊查找 以产品名开始,以%结束,例如:空%
@Test
public void testFindProductByName_1(){
Product p = new Product();
p.setProductName("空");
//按照产品名模糊查找 以产品名开始,以%结束,例如:空%
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
.withMatcher("productName",ExampleMatcher.GenericPropertyMatchers.startsWith());
Example<Product> example = Example.of(p,exampleMatcher);
productDao.findAll(example).forEach(System.out::println);
}
//按照产品名模糊查找 以%开始,以产品名结束,例如:%调
@Test
public void testFindProductByName_2(){
Product p = new Product();
p.setProductName("调");
//%调
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
.withMatcher("productName",ExampleMatcher.GenericPropertyMatchers.endsWith());
Example<Product> example = Example.of(p,exampleMatcher);
productDao.findAll(example).forEach(System.out::println);
}
//按照产品名模糊查找 以%开始,以%结束,中间拼接产品。例如:%空%
@Test
public void testFindProductByName_3(){
Product p = new Product();
p.setProductName("空");
//按照产品名模糊查找 以%开始,以%结束,中间拼接产品。例如:%空%
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
.withMatcher("productName",ExampleMatcher.GenericPropertyMatchers.contains());
Example<Product> example = Example.of(p,exampleMatcher);
productDao.findAll(example).forEach(System.out::println);
}
//按照产品名模糊查找 以%开始,以%结束,中间拼接产品名,并忽略id。例如:%空%
@Test
public void testFindProductByName_4(){
Product p = new Product();
p.setProductName("空");
p.setProductId(7);
//按照产品名模糊查找 以%开始,以%结束,中间拼接产品。例如:%空%
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
.withMatcher("productName",ExampleMatcher.GenericPropertyMatchers.contains()).withIgnorePaths("productId");//忽略id
Example<Product> example = Example.of(p,exampleMatcher);
productDao.findAll(example).forEach(System.out::println);
}
使用lambda表达式简化自定义匹配器规则:
@Test
public void testFindProductByName_5(){
Product p = new Product();
p.setProductName("空");
p.setProductId(7);
//按照产品名模糊查找 以%开始,以%结束,中间拼接产品。例如:%空%
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
.withMatcher("productName",match->match.contains())
.withIgnorePaths("productId");
Example<Product> example = Example.of(p,exampleMatcher);
productDao.findAll(example).forEach(System.out::println);
}
StringMatcher 参数
Matching | 生成的语句 | 说明 |
---|---|---|
DEFAULT (case-sensitive) | firstname = ?0 | 默认(大小写敏感) |
DEFAULT (case-insensitive) | LOWER(firstname) = LOWER(?0) | 默认(忽略大小写) |
EXACT (case-sensitive) | firstname = ?0 | 精确匹配(大小写敏感) |
EXACT (case-insensitive) | LOWER(firstname) = LOWER(?0) | 精确匹配(忽略大小写) |
STARTING (case-sensitive) | firstname like ?0 + ‘%’ | 前缀匹配(大小写敏感) |
STARTING (case-insensitive) | LOWER(firstname) like LOWER(?0) + ‘%’ | 前缀匹配(忽略大小写) |
ENDING (case-sensitive) | firstname like ‘%’ + ?0 | 后缀匹配(大小写敏感) |
ENDING (case-insensitive) | LOWER(firstname) like ‘%’ + LOWER(?0) | 后缀匹配(忽略大小写) |
CONTAINING (case-sensitive) | firstname like ‘%’ + ?0 + ‘%’ | 模糊查询(大小写敏感) |
CONTAINING (case-insensitive) | LOWER(firstname) like ‘%’ + LOWER(?0) + ‘%’ | 模糊查询(忽略大小写) |
说明:在默认情况下(没有调用withIgnoreCase())都是大小写敏感的。
关于参数的更加详细的说明,请参考官网:https://docs.spring.io/spring-data/jpa/docs/2.2.6.RELEASE/reference/html/#query-by-example.execution
5.6 方法命名规则查询:
顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询
按照Spring Data JPA 定义的规则,查询方法以findBy、getBy、queryBy(或者关键字加上All或者加上实体类名By)开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
//按照id和商品名查询商品信息
Product findProductByProductIdAndProductName(int id,String name);
//按照商品名模糊查询用户信息 生成的SQL语句最后以like?关键字为结尾
List<Product> findByProductNameLike(String name);
//按照商品名模糊查询 from Product where proudctName like %,?,%
List<Product> (String name);
//按照商品名模糊查询 from Product where proudctName like ?%
List<Product> findAllByProductNameStartingWith(String name);
//按照商品名模糊查询 from Product where proudctName like %?
List<Product> findAllByProductNameEndingWith(String name);
//按照一组id查询商品信息并根据id进行降序排列
//from product where product_id in (? , ? , ?)order by product_id desc
List<Product> getAllByProductIdInOrderByProductIdDesc(Integer... ids);
具体的关键字,使用方法和生产成SQL如下表所示
Keyword | Sample | JPQL |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs, findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
Keyword | Sample | JPQL |
---|---|---|
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
5.7 Specification动态查询:
有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象
对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。
Specification接口中只定义了如下一个抽象方法:
//构造查询条件
/**
* root :Root接口,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象(如where),用来自定义查询
* cb :用来构建查询,此对象里有很多条件方法(如like)
**/
@Nullable
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
1. 使用Specification完成条件查询
//根据ID查询单个产品信息 构建单个查询条件
//root 代表查询的根对象,可以通过root获取实体中的属性
//cq 是顶层查询条件:from where
//cb 底层的查询条件: in like = > <
@Test
public void testFindBySpecification_1(){
Specification<Product> specification = new Specification<Product>() {
@Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
return cq.where(cb.equal(root.get("productId"),1)).getRestriction();
}
};
Optional<Product> optionalProduct = productDao.findOne(specification);
if (optionalProduct.isPresent()) {
System.out.println(optionalProduct.get());
}
}
//根据ID,产品名查询单个产品信息 使用specification构建复杂查询条件
//root 代表查询的根对象,可以通过root获取实体中的属性
//cq 是顶层查询条件:from where
//cb 底层的查询条件: in like = > <
@Test
public void testFindBySpecification_2(){
Specification<Product> specification = new Specification<Product>() {
@Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
return cq.where(cb.equal(root.get("productId"),1),
//匹配id equal相等于条件中的=
//匹配name
cb.equal(root.get("productName"),"电脑")).getRestriction();
}
};
Optional<Product> optionalProduct = productDao.findOne(specification);
if (optionalProduct.isPresent()) {
System.out.println(optionalProduct.get());
}
}
//根据多个ID,查询多个产品信息 使用specification构建复杂查询条件
//root 代表查询的根对象,可以通过root获取实体中的属性
//cq 是顶层查询条件:from where
//cb 底层的查询条件: in like = > <
@Test
public void testFindBySpecification_3(){
Specification<Product> specification = new Specification<Product>() {
@Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
return cq.where(cb.in(root.get("productId"))
//相当于查询条件中的 in(?,?,?)关键字
.value(1).value(2).value(7).value(8)).getRestriction();
}
};
productDao.findAll(specification).forEach(System.out::println);
}
//根据产品名,模糊查询多个产品信息 使用specification构建复杂查询条件
//root 代表查询的根对象,可以通过root获取实体中的属性
//cq 是顶层查询条件:from where
//cb 底层的查询条件: in like = > <
@Test
public void testFindBySpecification_4(){
Specification<Product> specification = (root, cq, cb)->
cq.where(cb.like(root.get("productName"),'%'+"空"+"%")).getRestriction()
//相当于查询条件中的 like ?关键字).getRestriction();
;
Specification<Product> specification = new Specification<Product>() {
@Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
//相当于查询条件中的 like ?关键字
return cq.where(cb.like(root.get("productName"),'%'+"空"+"%")).getRestriction();
}
};
productDao.findAll(specification).forEach(System.out::println);
}
可使用lambda表达式简化:
//根据ID查询单个产品信息 使用specification构建查询条件
//root 代表查询的根对象,可以通过root获取实体中的属性
//cq 是顶层查询条件:from where
//cb 底层的查询条件: in like = > <
@Test
public void testFindBySpecification_1(){
Specification<Product> specification =
(root,cq,cb)->cq.where(cb.equal(root.get("productId"),1)).getRestriction();
Optional<Product> optionalProduct = productDao.findOne(specification);
if (optionalProduct.isPresent()) {
System.out.println(optionalProduct.get());
}
}
//根据ID,产品名查询单个产品信息 使用specification构建复杂查询条件
//root 代表查询的根对象,可以通过root获取实体中的属性
//cq 是顶层查询条件:from where
//cb 底层的查询条件: in like = > <
@Test
public void testFindBySpecification_2(){
Specification<Product> specification = (root, cq, cb)->
cq.where(cb.equal(root.get("productId"),1),//匹配id equal相等于条件中的=
//匹配name
cb.equal(root.get("productName"),"电脑")).getRestriction();
;
Optional<Product> optionalProduct = productDao.findOne(specification);
if (optionalProduct.isPresent()) {
System.out.println(optionalProduct.get());
}
}
//根据多个ID,查询多个产品信息 使用specification构建复杂查询条件
//root 代表查询的根对象,可以通过root获取实体中的属性
//cq 是顶层查询条件:from where
//cb 底层的查询条件: in like = > <
@Test
public void testFindBySpecification_3(){
Specification<Product> specification = (root, cq, cb)->
cq.where(cb.in(root.get("productId"))//相当于查询条件中的 in(?,?,?)关键字
.value(1).value(2).value(7).value(8)).getRestriction();
productDao.findAll(specification).forEach(System.out::println);
}
//根据产品名,模糊查询多个产品信息 使用specification构建复杂查询条件
//root 代表查询的根对象,可以通过root获取实体中的属性
//cq 是顶层查询条件:from where
//cb 底层的查询条件: in like = > <
@Test
public void testFindBySpecification_4(){
Specification<Product> specification = (root, cq, cb)->
cq.where(cb.like(root.get("productName"),"%"+"空"+"%")).getRestriction();
//相当于查询条件中的 like ?关键字).getRestriction();
productDao.findAll(specification).forEach(System.out::println);
}
2. 基于Specification的分页查询
/**
* 测试分页查询
*/
@Test
public void testPages(){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
ProductDao productDao = context.getBean(ProductDao.class);
Specification<Product> specification = new Specification<Product>() {
@Override
public Predicate toPredicate(Root<Product> root,
CriteriaQuery<?> cq,
CriteriaBuilder cb) {
return cb.like(root.get("productName").as(String.class),"%%");
}
};
/**
* 构造分页参数
* Pageable : 接口
* PageRequest实现了Pageable接口,调用PageRequest.of
* 第一个参数:页码(从0开始)
* 第二个参数:每页查询条数
*/
Pageable pageRequest = PageRequest.of(0,2);
/**
* 分页查询,封装为Spring Data Jpa 内部的page bean
* 此重载的findAll方法为分页方法需要两个参数
* 第一个参数:查询条件Specification
* 第二个参数:分页参数
*/
Page<Product> productPage = productDao.findAll(specification, pageRequest);
productPage.forEach(p -> log.info("p=>{}",p));
int totalPages = productPage.getTotalPages();//总页数
long totalElements = productPage.getTotalElements();//总记录数
int size = productPage.getSize();//每页的记录数
log.info("totalPages=>{}",totalPages);
log.info("totalElements=>{}",totalElements);
log.info("size=>{}",size);
}
使用Lambda表达式简化:
/**
* 测试分页查询
*/
@Test
public void testPages(){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
ProductDao productDao = context.getBean(ProductDao.class);
/**
* 构造分页参数
* Pageable : 接口
* PageRequest实现了Pageable接口,调用PageRequest.of
* 第一个参数:页码(从0开始)
* 第二个参数:每页查询条数
*/
Pageable pageRequest = PageRequest.of(0,2);
/**
* 分页查询,封装为Spring Data Jpa 内部的page bean
* 此重载的findAll方法为分页方法需要两个参数
* 第一个参数:查询条件Specification
* 第二个参数:分页参数
*/
Page<Product> productPage =
productDao.findAll((root,cq,cb)->cb.like(root.get(
"productName").as(String.class),"%%"),pageRequest);
productPage.forEach(p -> log.info("p=>{}",p));
int totalPages = productPage.getTotalPages();//总页数
long totalElements = productPage.getTotalElements();//总记录数
int size = productPage.getSize();//每页的记录数
log.info("totalPages=>{}",totalPages);
log.info("totalElements=>{}",totalElements);
log.info("size=>{}",size);
}
对于Spring Data JPA中的分页查询,是其内部自动实现的封装过程,返回的是一个Spring Data JPA提供的pageBean对象。其中的常用方法说明如下:
//获取总页数
int getTotalPages();
//获取总记录数
long getTotalElements();
//获取列表数据
List<T> getContent();
多个条件甚至需要排序时,请参考如下代码:
/**
* 测试按照姓名模糊查询按照角色精确查询并且按照创建日期排序 然后分页查询
*/
@Test
public void testPages2(){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
UserDao userDao = context.getBean(UserDao.class);
/**
* 构造分页参数
* Pageable : 接口
* PageRequest实现了Pageable接口,调用PageRequest.of
* 第一个参数:页码(从0开始)
* 第二个参数:每页查询条数
*/
Pageable pageRequest = PageRequest.of(0,2,Sort.by("creationDate").descending());
/**
* 分页查询,封装为Spring Data Jpa 内部的page bean
* 此重载的findAll方法为分页方法需要两个参数
* 第一个参数:查询条件Specification
* 第二个参数:分页参数
*/
Page<User> userDaoPage =
userDao.findAll((root,cq,cb)->
cq.where(cb.equal(root.get("userRole").as(Integer.class),3),
cb.like(root.get("userName").as(String.class)
,"%孙%")).orderBy(new OrderImpl(root.get("id"),false)).getRestriction()
,pageRequest);
userDaoPage.forEach(p -> log.info("p=>{}",p));
int totalPages = userDaoPage.getTotalPages();//总页数
long totalElements = userDaoPage.getTotalElements();//总记录数
int size = userDaoPage.getSize();//每页的记录数
log.info("totalPages=>{}",totalPages);
log.info("totalElements=>{}",totalElements);
log.info("size=>{}",size);
}
也可以使用CriteriaBuilder构造顶层的查询条件,代码如下:
//cb 底层的查询条件,也可以构建顶层的多个查询条件: in like = > <
@Test
public void testFindBySpecification_10(){
Specification<Product> specification = (root, cq, cb)->
//多表的连接时 可以调用root.join方法来实现连接条件的编写
cb.and(cb.gt(root.get("productId"),7),cb.like(root.get("productName"),'%'+"空"+"%"))
//相当于查询条件中的 like ?关键字).getRestriction();
;
System.out.println(productDao.count(specification));
}
3. CriteriaBuilder方法对应关系
方法名称 | Sql对应关系 |
---|---|
equal | filed = value |
gt(greaterThan ) | filed > value |
lt(lessThan ) | filed < value |
ge(greaterThanOrEqualTo ) | filed >= value |
le( lessThanOrEqualTo) | filed <= value |
notEqule | filed != value |
like | filed like value |
notLike | filed not like value |
6. SpringBoot整合Spring Data JPA
6.1 导入依赖:
引入 Spring Boot Starter 父工程:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
引入 spring-boot-starter
、spring-boot-starter-test
、spring-boot-starter-data-jpa
、mysql
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
6.2 添加配置:
application.properties
文件中添加配置:
# 数据库驱动可以不用配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/smbms?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
# Hibernate根据给定的实体类结构更改数据库中映射的表。
spring.jpa.hibernate.ddl-auto=update
其它的参数设置以及参数说明请参考官网:https://spring.io/guides/gs/accessing-data-mysql/
6.3 编写代码:
编写Product.java
,代码如下:
@Entity
@Table(name = "product")
//@Proxy(lazy = false)//启用立即加载
public class Product implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
// @Column(name = "product_id")
private Integer productId;
// @Column(name = "product_name")
private String productName;
//省略getter setter 以及 toString()
}
编写ProductDao.java
,代码如下:
public interface ProductDao extends CrudRepository<Product,Integer> {
}
6.4 功能测试:
编写测试代码,如下:
@SpringBootTest
//@RunWith(SpringRunner.class) //如果使用org.junit.Test 则必须添加注解RunWith
class SsdDemoApplicationTests {
@Autowired
ProductDao dao;
@Test//注意test注解别引入错了 正确的是:org.junit.jupiter.api.Test;
void contextLoads() {
dao.findAll().forEach(System.out::println);
}
}
结果如下:
其它可用接口,可参照官网:https://spring.io/guides/gs/accessing-data-jpa/
至于Spring Data JPA 各种API的编写,上面的案例已经演示说明,此处不再赘述!!