SpringData JPA


1 持久层开发的问题

随着互联网技术的发展,现在的企业开发中用到的用于数据存储的产品,不再仅仅是关系型数据库,而是要根据场景需要选择不同的存储技术,比如用于缓存热点数据的redis,用于存储文档数据的mongodb,用于支持强大搜索功能的elasticsearch等等。

在这里插入图片描述

​  在Java中,对于上面所说的产品都提供了优秀的访问技术。比如针对关系型数据库的mybatis、jpa等技术,针对于redis的jedis技术等等… 这些技术虽然可以很好的针对各个存储产品进行访问操作,但同时也带来了新的问题,那就是不同的持久层技术的API是不一样的。

这样一来,开发人员就必须同时掌握多种数据访问技术,这无疑增加了开发成本。那么我们会想,有没有这样一种技术,它可以使用一套API支持各个不同的存储的访问呢?就在这样的需求下,SpringData产生了。

在这里插入图片描述

1 SpringData简介

2.1 什么是SpringData

SpringData是一个用来简化dao层开发的框架。它在保证了各个底层存储特性的同时,提供了一套统一的数据访问API。它可以很好的支持常用的关系型数据库和非关系型数据库。

使用SpringData作为dao层开发技术,将大大简化代码量,而且其API比各个技术的原生API更加简单易用。

2.2 SpringData的主要模块

SpringData支持的持久层技术非常多,我们只介绍几个常见的:

  • Spring Data common    SpringData的核心模块,定义了SpringData的核心功能

  • Spring Data JDBC      对JDBC的Spring Data存储库支持

  • Spring Data JPA      对JPA的Spring Data存储库支持

  • Spring Data MongoDB   对MongoDB的基于Spring对象文档的存储库支持

  • Spring Data Redis     封装Jedis技术,对redis实现访问操作

  • Spring Data Elasticsearch  对Elasticsearch实现访问操作

3 JPA基础

Hibernate是一个全自动的ORM框架,是对 JDBC技术的封装。它在实体类和数据库表之间建立了映射关系,使得程序员可以使用面向对象编程思维来操纵数据库,而Hibernate会自动给我们生成 SQL 语句。

JPA 的全称是 Java Persistence API,即 Java 持久化 API,是 SUN 公司推出的一套基于 ORM 的规范,注意不是 ORM 框架——因为 JPA 并未提供 ORM 实现,它只是提供了一些编程的 API 接口。

在这里插入图片描述

4 JPA实战

4.1 目标

本章节我们是实现的功能是搭建Jpa环境,并实现一条数据的增删改查。

4.2 准备数据库环境

--准备数据库,创建一张文章表备用
CREATE TABLE `article` (
  `aid` int(11) NOT NULL auto_increment COMMENT '主键',
  `author` varchar(255) default NULL COMMENT '作者',
  `createTime` datetime default NULL COMMENT '创建时间',
  `title` varchar(255) default NULL COMMENT '标题',
  PRIMARY KEY  (`aid`)
);

4.3 创建java工程,导入坐标

<dependencies>
    <!--Jpa的支撑框架hibernate-->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.0.7.Final</version>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
    <!-- 日志 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <!-- 单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

4.4 创建实体类

public class Article  implements Serializable {
    private Integer aid;
    private String title;
    private String author;
    private Date createTime;
	//省略set和get方法。。。
    //省略toString方法。。。
}    

4.5在实体类中配置映射关系

@Entity//表示这是一个实体类
@Table(name = "article") //建立实体类和表的映射关系
public class Article implements Serializable {

    @Id//声明当前私有属性为主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //配置主键的生成策略
    private Integer aid;

    //声明类的属性跟数据表字段的对应关系,如果属性名称和字段名称一致,可省略
    @Column(name = "title")
    private String title;
    private String author;
    private Date createTime;
}

4.6 加入 JPA 的核心配置文件

在maven工程的resources路径下创建一个名为META-INF的文件夹,在文件夹下创建一个名为persistence.xml的配置文件。注意: META-INF文件夹名称不能修改,persistence.xml文件名称不能改。
<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!--持久化单元-->
    <persistence-unit name="springdata" transaction-type="RESOURCE_LOCAL">
        <!--配置 JPA 规范的服务提供商 -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <!-- 数据库驱动 -->
            <property name="javax.persistence.jdbc.driver" 
                      value="com.mysql.jdbc.Driver"/>
            <!-- 数据库地址 -->
            <property name="javax.persistence.jdbc.url"
                      value="jdbc:mysql:///springdata"/>
            <!-- 数据库用户名 -->
            <property name="javax.persistence.jdbc.user" value="root"/>
            <!-- 数据库密码 -->
            <property name="javax.persistence.jdbc.password" value="adminadmin"/>
            <!--jpa的核心配置中兼容hibernate的配置-->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
</persistence>

4.7 测试

4.7.1 实现保存操作

@Test
public void testSave() {
    //1 创建持久化管理器工厂
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("springdata");

    //2 创建持久化管理器
    EntityManager entityManager = factory.createEntityManager();

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

    //4 操作
    Article article = new Article();
    article.setTitle("测试文章");
    article.setAuthor("oldlu");
    article.setCreateTime(new Date());
    entityManager.persist(article);

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

    //6 关闭资源
    entityManager.close();
}

4.7.2 实现查询操作

@Test
public void testFindByAid() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("springdata");
    EntityManager entityManager = factory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin();

    Article article = entityManager.find(Article.class, 1);
    System.out.println(article);

    transaction.commit();
    entityManager.close();
}

4.7.3 实现修改操作

@Test
public void testUpdate() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("springdata");
    EntityManager entityManager = factory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin();

    Article article = entityManager.find(Article.class, 1);
    //修改
    article.setAuthor("oldlu程序员");

    transaction.commit();
    entityManager.close();
}

4.7.4 实现删除操作

@Test
public void testDelete() {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("springdata");
    EntityManager entityManager = factory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin();

    Article article = entityManager.find(Article.class, 1);
    //删除
    entityManager.remove(article);
    transaction.commit();
    entityManager.close();
}

5 JPA的重要API介绍

5.1 EntityManagerFactory

EntityManagerFactory接口主要用来创建EntityManager实例
EntityManagerFactory是一个线程安全的对象,并且其创建极其浪费资源,所以编程的时候要保持它是单例的。

5.2 EntityManager

在JPA规范中,EntityManager是操作数据库的重要API,他是线程不安全的,需要保持线程独有。
重要方法说明:
    getTransaction: 获取事务对象
    persist:保存操作
    merge:更新操作
    remove:删除操作
    find/getReference:根据id查询

5.3 save和saveAndFlush方法区别

On saveAndFlush, changes will be flushed to DB immediately in this command. With save, this is not necessarily true, and might stay just in memory, until flush or commit commands are issued.

在saveAndFlush上,此命令中的更改将立即刷新到DB。使用save,就不一定了,它可能只暂时保留在内存中,直到发出flush或commit命令。

But be aware, that even if you flush the changes in transaction and do not commit them, the changes still won’t be visible to the outside transactions until the commit in this transaction.

但是要注意的是,即使在事务中刷新了更改并且未提交它们,这些更改对于外部事务仍然不可见,直到,提交这个事务。

In your case, you probably use some sort of transactions mechanism, which issues commit command for you if everything works out fine.

在您的情况下,您可能使用某种事务机制,如果一切正常,它会为您发出commit命令。

5.3.1 总结

比如在我们得项目中,保存一条数据后,我又想立马用到这条数据得id,因为实体类是配置了uuid自动生成,所以使用saveAndFlush()方法就可以立即获取到这条数据得id。但是如果用sava()方法,你不flush()或者commit,你得数据是暂时只在内存中保存,所以此时这条数据是没有主键id的.

提交事务后数据插入进数据库,要想在事务提交之前避免缓存插入数据库需要在执行完save操作调用flush方法或者直接执行saveAndFlush方法即可

6 SpringData JPA简介

SpringData JPA是Spring Data家族的一个成员,是Spring Data对JPA封装之后的产物,目的在于简化基于JPA的数据访问技术。使用SpringData JPA技术之后,开发者只需要声明Dao层的接口,不必再写实现类或其它代码,剩下的一切交给SpringData JPA来搞定 。

7SpringData JPA快速入门

7.1 目标

本章节我们是实现的功能是搭建SpringData JPA环境,并实现一条数据的增删改查。

7.2 准备数据环境

--下面的操作让JPA自动生成表结构

7.3 创建java工程,导入坐标

<dependencies>
    <!-- 日志 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <!-- 单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

    <!-- Spring框架相关jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.1.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>

    <!--jpa-->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>2.1.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.0.7.Final</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
</dependencies>

7.4 创建实体类

public class Article  implements Serializable {
    private Integer aid;
    private String title;
    private String author;
    private Date createTime;
	//省略set和get方法。。。
    //省略toString方法。。。
}    

7.5 在实体类中配置映射关系

@Entity//表示这是一个实体类
@Table(name = "article") //建立实体类和表的映射关系
public class Article implements Serializable {

    @Id//声明当前私有属性为主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //配置主键的生成策略
    private Integer aid;

    //声明类的属性跟数据表字段的对应关系,如果属性名称和字段名称一致,可省略
    @Column(name = "title")
    private String title;
    private String author;
    private Date createTime;
    //省略set和get方法。。。
    //省略toString方法。。。
}

7.6 编写dao接口

使用 Spring Data JPA操作数据库,只需要按照框架的规范提供 dao 接口,不需要提供在接口中定义方法,也不需要为接口提供实现类就能完成基本的数据库的增删改查等功能。

在 Spring Data JPA 中,对于定义符合规范的 Dao 层接口,我们只需要遵循以下几点就可以了:

  1. 创建一个 Dao 层接口,并实现 JpaRepository 和 JpaSpecificationExecutor
  2. 提供相应的泛型
/**
 * JpaRepository<实体类类型,主键类型>:用来完成基本 CRUD 操作
 * JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
 */
public interface ArticleDao extends JpaRepository<Article, Integer>,
		JpaSpecificationExecutor<Article> { 
}

7.7 添加Spring整合Jpa的配置文件

<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

	<!-- 配置要扫描的包 -->
	<context:component-scan base-package="net.csdn" />

	<!-- 1.dataSource -->
	<bean id="dataSource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql:///springdata" />
		<property name="username" value="root" />
		<property name="password" value="adminadmin" />
	</bean>

	<!-- 2.EntityManagerFactory -->
	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<!-- 注入数据源 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 指定实体类所在的包 -->
		<property name="packagesToScan" value="net.csdn.domain" />
		<!-- 指定jpa的实现提供者 -->
		<property name="persistenceProvider">
			<bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
		</property>
		<!--JPA供应商适配器 -->
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<!-- 是否生成DDL语句   是否自动建表 -->
				<property name="generateDdl" value="true" />
				<!-- 数据库厂商名称 -->
				<property name="database" value="MYSQL" />
				<!-- 数据库方言 -->
				<property name="databasePlatform" 
                          value="org.hibernate.dialect.MySQLDialect" />
				<!-- 是否显示SQL -->
				<property name="showSql" value="true" />
			</bean>
		</property>
	</bean>

	<!-- 3.事务管理器 -->
	<bean id="transactionManager" 
          class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	<!-- 整合spring data jpa -->
	<!--spring 通过代理的方式为dao接口提供实现类,需要指明为哪些接口去产生代理类-->
	<jpa:repositories base-package="net.csdn.dao"
		transaction-manager-ref="transactionManager"
		entity-manager-factory-ref="entityManagerFactory" />
</beans>

7.8 测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ArticleDaoTest {

    @Autowired
    private ArticleDao articleDao;

    //新增
    @Test
    public void testSave() {
        Article article = new Article();
        article.setTitle("测试文章");
        article.setAuthor("oldlu");
        article.setCreateTime(new Date());
        articleDao.save(article);
    }

    //修改
    @Test
    public void testUpdate() {
        Article article = new Article();
        article.setTitle("测试文章1");
        article.setContent("测试内容");
        article.setAuthor("oldlu");
        article.setCreateTime(new Date());
        article.setAid(1);
        articleDao.save(article);
    }

    //根据Id查询
    @Test
    public void testFindByAid() {
        Optional<Article> optional = articleDao.findById(1);
        System.out.println(optional.get());
    }

    //查询所有
    @Test
    public void testFindAll() {
        List<Article> list = articleDao.findAll();
        for (Article article : list) {
            System.out.println(article);
        }
    }

    //删除
    @Test
    public void testdelete() {
        articleDao.deleteById(1);
    }
}

8 SpringData Jpa运行原理分析

8.1 SpringData中的几个重要接口

思考一个问题:自定义的接口中没有写任何的方法声明,那么测试类中调用的接口中的方法是哪来的呢?

自定义的接口继承了两个接口,方法肯定来自里面,追踪关系得到下面的继承关系
    Repository 标记接口:继承了此接口后会被Spring识别,进而可以在接口中声明一些满足规范的方法
        |
        |
    CrudRepository 实现了基本增删改查方法
        | 
        |
    PagingAndSortingRepository 实现了分页和排序的方法
        | 
        |
    JpaRepository 重写了几个查找和删除的方法
        | 
        |
    ArticleDao
  通过上面的继承关系,我们可以看到我们自定义的接口ArticleDao继承了一系列的Repository接口,而每一个接口都会给我们提供一部分的功能,这样继承下来,我们的ArticleDao不用任何的方法声明就拥有了很多的功能了。

8.2 SpringData Jpa底层运行原理

思考一个问题:我们找到了定义方法的接口,但并没有看到实现类,没有实现来就无法创建对象,那么真正干活的实现类到底在哪,它又是如何产生对象的呢?

下面我们通过debug的形式,寻找答案:

  1. 在运行时,Spring会使用JdkDynamicAopProxy为dao接口生成一个代理对象
    在这里插入图片描述

  2. 那么这个代理对象是根据那个类代理出来的呢?点击进入JdkDynamicAopProxy源码查看invoke方法,发现targetSource代理的是SimpleJpaRepository类

在这里插入图片描述

  1. 通过对SimpleJpaRepository中代码的分析,我们看到最终执行保存的是EntityManager对象
    在这里插入图片描述

总结:使用SpringData JPA开发底层还是用的JPA的API,SpringData JPA只是对标准 JPA 操作进行了进一步封装,已达到简化了Dao层代码开发的目的。

8.3 SpringData Jpa 与 Jpa 及 Hibernate的关系

在这里插入图片描述

9 SpringData JPA的多种查询方式

9.1 父接口方法查询

我们自定义的Dao接口可以使用它的父接口提供的方法,可以使用的方法如下图所示。

在这里插入图片描述

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-jpa.xml")
public class SpringDataJpaQueryTest {

    @Autowired
    private ArticleDao articleDao;

    //主键查询
    @Test
    public void testFindById() {
        Optional<Article> optional = articleDao.findById(21);
        System.out.println(optional.get());
    }

    //查询所有
    @Test
    public void testFindAll() {
        List<Article> articles = articleDao.findAll();
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    //查询所有---排序
    @Test
    public void testFindAllWithSort() {
        Sort sort = Sort.by(Sort.Order.desc("aid"));
        List<Article> articles = articleDao.findAll(sort);
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    //查询所有---分页
    @Test
    public void testFindAllWithPage() {
        //从第几页(页数从0开始)开始查,每页多少条
        Pageable pageable = PageRequest.of(2,3);
        Page<Article> page = articleDao.findAll(pageable);
        for (Article article : page.getContent()) {
            System.out.println(article);
        }
    }

    //查询所有---分页和排序
    @Test
    public void testFindAllWithPageAndSort() {
        Sort sort = Sort.by(Sort.Order.desc("aid"));
        //从第几页(页数从0开始)开始查,每页多少条
        Pageable pageable = PageRequest.of(2,3,sort);
        Page<Article> page = articleDao.findAll(pageable);
        for (Article article : page.getContent()) {
            System.out.println(article);
        }
    }

}

9.2 方法命名规则查询

   顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照SpringData JPA提供的方
法命名规则定义方法的名称,就可以完成查询工作。
   SpringData JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询.
   按照SpringData JPA定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
public interface ArticleDao extends JpaRepository<Article, Integer>, JpaSpecificationExecutor<Article> {

    //根据标题查询
    List<Article> findByTitle(String title);

    //根据标题模糊查询
    List<Article> findByTitleLike(String title);

    //根据标题和作者查询
    List<Article> findByTitleAndAuthor(String title, String author);

    //根据ID范围查询
    List<Article> findByAidBetween(Integer starAid, Integer endAid);

    List<Article> findByAidLessThan(Integer endAid);

    List<Article> findByAidIn(List<Integer> aids);

    //根据创建时间之后查询
    List<Article> findByCreateTimeAfter(Date createTime);
关键字例子对应的JPQL语句
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ? 2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs, findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = U

9.3 JPQL查询

   使用SpringData JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询。
  JPQL,全称是Java Persistence Query Language。JPQL语句是JPA中定义的一种查询语言,此种语言的用意是让开发者忽略数据库表和表中的字段,而关注实体类及实体类中的属性。
  它的写法十分类似于SQL语句的写法,但是要把查询的表名换成实体类名称,把表中的字段名换成实体类的属性名称。 
public interface ArticleDao extends JpaRepository<Article, Integer>, 
									JpaSpecificationExecutor<Article> {


    //展示位置参数绑定
    @Query("from Article a where a.author=?1 and a.title=?2")
    List<Article> findByCondition1(String author, String title);

    //展示名字参数绑定
    @Query("from Article a where a.author=:author and a.title=:title")
    List<Article> findByCondition2(@Param("author") String author, 
                                   @Param("title") String title);

    //展示like模糊查询
    @Query("from Article a where a.title like %:title%")
    List<Article> findByCondition3(@Param("title") String title);

    //展示排序查询
    @Query("from Article a where a.title like %:title% order by aid desc")
    List<Article> findByCondition4(@Param("title") String title);

    //展示分页查询
    @Query("from Article a where a.title like %:title%")
    Page<Article> findByCondition5(Pageable pageable, @Param("title") String title);

    //展示传入集合参数查询
    @Query("from Article a where a.aid in :aids")
    List<Article> findByCondition6(@Param("aids") Collection<String> aids);

    //展示传入Bean进行查询(SPEL表达式查询)
    @Query("from Article a where a.author=:#{#article.author} and a.title=:#{#article.title}")
    Article findByCondition67(@Param("article") Article article);
}

9.4 本地SQL查询

//nativeQuery=true表示使用本地SQL查询
//基本不会使用,除非是出现非常复杂的业务情况导致SQL非常复杂,JPQL搞不定的时候
@Query("select * from article where title like ?1 and author = ?2",nativeQuery=true)
List<User> findAllByTitleAndAuthor(String title,String author);

9.5 Specifications动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在 Spring
Data JPA 中可以通过 JpaSpecificationExecutor 接口查询。相比 JPQL,其优势是类型安全,更加的面向对象,缺点是书写比较麻烦。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ArticleDao4Test {

    @Autowired
    private ArticleDao articleDao;

    @Test
    public void test1() {
        List<Article> articles = articleDao.findAll(new Specification<Article>() {
            //查找title中含有“张三”,并且aid> 3的文章
            String myTitle = "张三";
            Integer myAid = 3;

            /**
             *
             * @param root  用于获取属性
             * @param cq    用于生成SQL 一般用不到
             * @param cb    用于追加添加条件
             * @return
             */
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq, 
                                         CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();
                if (myTitle != null && !"".equals(myTitle)) {
                    Predicate predicate = 
                        cb.like(root.get("title").as(String.class), "%" + myTitle + "%");
                    predicates.add(predicate);
                }
                if (myAid != null) {
                    Predicate predicate =
                        cb.greaterThan(root.get("aid").as(Integer.class), myAid);
                    predicates.add(predicate);
                }

                return cb.and(predicates.toArray(new Predicate[]{}));
            }
        });
        for (Article article : articles) {
            System.out.println(article);
        }
    }
   
    //分页查询
    @Test
    public void test2() {
        int pageStart = 0;//起始页码
        int pageSize = 2;//每页条数
        Pageable pageable = PageRequest.of(pageStart, pageSize);
        Page<Article> pages = articleDao.findAll(new Specification<Article>() {
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq,
                                         CriteriaBuilder cb) {
                //代表查询全部
                return null;
            }
        }, pageable);
        System.out.println("总记录数:" + pages.getTotalElements());
        System.out.println("总页数:" + pages.getTotalPages());
        System.out.println("当前页:" + pages.getNumber());
        for (Article article : pages.getContent()) {
            System.out.println(article);
        }
    }
    
    //分页+排序
    @Test
    public void test3() {
        int pageStart = 0;//起始页码
        int pageSize = 2;//每页条数
        //Sort.by(Sort.Order.desc("属性"))
        Pageable pageable = PageRequest.of(pageStart, pageSize,
                                           Sort.by(Sort.Order.desc("aid")));

        Page<Article> pages = articleDao.findAll(new Specification<Article>() {
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq,
                                         CriteriaBuilder cb) {
                //代表查询全部
                return null;
            }
        }, pageable);

        System.out.println("总记录数:" + pages.getTotalElements());
        System.out.println("总页数:" + pages.getTotalPages());
        System.out.println("当前页:" + pages.getNumber());
        for (Article article : pages.getContent()) {
            System.out.println(article);
        }
    }
    
}

9.6 时间范围查询

需要判断的时间字段String即可,只不过需要格式相同

    @Override
    public Page<TaskPrint> findTaskPrintPage(TaskPrintQueryParam taskPrintQueryParam) {
        log.info("入参->" + taskPrintQueryParam.toString());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Specification<TaskPrint> spec = (Specification<TaskPrint>) (root, query, cb) -> {
            /*文件名称、申请人、打印人、打印状态(未打印、已打印、全部,默认字段未打印)、打印时间段,倒叙排列。*/
            Path<String> isFinished = root.get("isFinished");
            Path<String> createTime = root.get("createTime");
            Path<String> printerName = root.get("printerName");
            Path<String> fileName = root.get("fileName");
            Path<String> applicantName = root.get("applicantName");
            Predicate and = null;
            if (taskPrintQueryParam.getApplicantName() != null) {
                and = cb.and(cb.equal(applicantName, taskPrintQueryParam.getApplicantName()));
            }
            if (taskPrintQueryParam.getPrinterName() != null) {
                and = cb.and(cb.like(printerName, "%" + taskPrintQueryParam.getPrinterName() + "%"));
            }
            if (taskPrintQueryParam.getIsFinished() != null) {
                and = cb.and(cb.equal(isFinished, taskPrintQueryParam.getIsFinished()));
            }
            if (taskPrintQueryParam.getFileName() != null) {
                and = cb.and(cb.like(fileName, "%" + taskPrintQueryParam.getFileName() + "%"));
            }
            if (taskPrintQueryParam.getStartDate() != null || taskPrintQueryParam.getEndDate() != null) {
                Predicate p = null;
                try {
                /**
                时间范围查询,这里要注意时间格式
                */
                    if (taskPrintQueryParam.getStartDate()!=null&&taskPrintQueryParam.getEndDate()!=null){
                        p = cb.between(createTime, taskPrintQueryParam.getStartDate(), taskPrintQueryParam.getEndDate());
                    }
                    if (taskPrintQueryParam.getStartDate()==null){
                        p = cb.between(createTime, "1970-01-01 00:00:00", taskPrintQueryParam.getEndDate());
                    }
                    if (taskPrintQueryParam.getEndDate()==null){
                        Date date = new Date();// 获取当前时间
//                       log.info("现在时间->"+sdf.format(date));
                        p = cb.between(createTime, taskPrintQueryParam.getStartDate(), sdf.format(date));
                    }
                    and = cb.and(p);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return and;
        };
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        Pageable pageable = PageRequest.of(taskPrintQueryParam.getPageNum()-1, taskPrintQueryParam.getPageSize(), sort);
        org.springframework.data.domain.Page<TaskPrint> taskPrints = taskPrintRepository.findAll(spec, pageable);
        Page page = new Page();
        if (taskPrints!=null){
            taskPrints.getContent().forEach(taskPrint -> {
                FileInfo fileInfoByForeignId = fileInfoRepository.findFileInfoByForeignId(taskPrint.getId());
                if (fileInfoByForeignId != null) {
                    taskPrint.setFileInfo(fileInfoByForeignId);
                }
                Printer byPrinterName = printerRepository.findPrinterById(taskPrint.getPrinterId());
                if (byPrinterName != null) {
                    taskPrint.setPrinter(byPrinterName);
                }
                Device deviceById = deviceRepository.findDeviceById(taskPrint.getDeviceId());
                if (deviceById != null) {
                    taskPrint.setDevice(deviceById);
                }
            });
            page.setList(taskPrints.getContent());
            page.setCount((int) taskPrints.getTotalElements());
            page.setPageNum(taskPrintQueryParam.getPageNum());
            page.setPageSize(taskPrintQueryParam.getPageSize());
        }

        return page;
    }

9.7 AND OR条件查询

  /**
     * 分页查询
     */
    @ApiOperation(value = "2. 分页条件查询案件排期", notes = "2. 分页条件查询案件排期")
    @GetMapping("/list")
    public Object list(CaseSchedulingVO schedulingVO,
                       @RequestParam(required = false, defaultValue = "0") int pageNumber,
                       @RequestParam(required = false, defaultValue = "10") int pageSize) {


        //分页构造
        Pageable pageable = PageRequest.of(pageNumber, pageSize);
        Page<CaseScheduling> pages = caseSchedulingRepository.findAll((Specification<CaseScheduling>) (root, cq, cb) -> {
            Path<String> caseNo = root.get("caseNo");
            Path<String> litignts = root.get("litignts");
            Path<String> caseReason = root.get("caseReason");
            Path<Integer> opened = root.get("opened");
            Path<Integer> isBurn = root.get("isBurn");
            Path<Integer> isMergedCasescheduling = root.get("isMergedCasescheduling");
            Path<Date> startTime = root.get("startTime");
            Path<Date> endTime = root.get("endTime");
            List<Predicate> adds = new ArrayList<>();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            if (schedulingVO.getSearch() != null) {
                Predicate andCaseNo = cb.or(cb.equal(caseNo, schedulingVO.getSearch()));
                Predicate andLitignts = cb.or(cb.like(litignts, "%" + schedulingVO.getSearch() + "%"));
                Predicate andCaseReason = cb.or(cb.like(caseReason, "%" + schedulingVO.getSearch() + "%"));
                adds.add(cb.or(andCaseNo, andLitignts, andCaseReason));
            }
            if (schedulingVO.getOpened() != null) {
                Predicate andOpened = cb.and(cb.equal(opened, schedulingVO.getOpened()));
                adds.add(andOpened);
            }
            if (schedulingVO.getIsBurn() != null) {
                Predicate andIsBurn = cb.and(cb.equal(isBurn, schedulingVO.getIsBurn()));
                adds.add(andIsBurn);
            }

            if (schedulingVO.getStartTime() != null || schedulingVO.getEndTime() != null) {
                Predicate p = null;
                if (schedulingVO.getStartTime() != null && schedulingVO.getEndTime() != null) {
                    p = cb.between(startTime, schedulingVO.getStartTime(), schedulingVO.getEndTime());
                }
                if (schedulingVO.getStartTime() == null) {
                    Date date = new Date();
                    date.setTime(0);
                    p = cb.between(startTime, date, schedulingVO.getEndTime());
                }
                if (schedulingVO.getEndTime() == null) {
                    Date date = new Date();// 获取当前时间
//                       log.info("现在时间->"+sdf.format(date));
                    p = cb.between(endTime, schedulingVO.getStartTime(), date);
                }
                adds.add(p);
            }

            Predicate andisMergedCasesOne = cb.or(cb.equal(isMergedCasescheduling, 0));
            Predicate andisMergedCasesTwo = cb.or(cb.equal(isMergedCasescheduling, 2));
            adds.add(cb.or(andisMergedCasesOne, andisMergedCasesTwo));
            return cb.and(adds.toArray(new Predicate[]{}));
        }, pageable);

        return pages;
    }

9.7 时间范围过滤

 /**
     * 判断排期是否冲突
     * @param caseScheduling
     */
    public void   isConflictSchedule(CaseScheduling caseScheduling) {
        //查询时间段内是否有数据
        List<CaseScheduling> caseSchedulings = caseSchedulingRepository.findAll((Specification<CaseScheduling>) (root, cq, cb) -> {
            Path<Integer> isMergedCasescheduling = root.get("isMergedCasescheduling");
            Path<Date> startTime = root.get("startTime");
            Path<Date> endTime = root.get("endTime");
            List<Predicate> adds = new ArrayList<>();
            Predicate p1 ;
            Predicate p2 ;
            if (caseScheduling.getStartTime() != null || caseScheduling.getEndTime() != null) {
                /**
                 *         queryWrapper.le("start_time", endTime);
                 *         queryWrapper.ge("end_time", startTime);
                 */
                p1 = cb.lessThanOrEqualTo(startTime, caseScheduling.getEndTime());
                p2 = cb.greaterThanOrEqualTo(endTime, caseScheduling.getStartTime());
                adds.add(p1);
                adds.add(p2);
            }
            Predicate andisMergedCasesOne = cb.or(cb.equal(isMergedCasescheduling, 0));
            Predicate andisMergedCasesTwo = cb.or(cb.equal(isMergedCasescheduling, 2));
            adds.add(cb.or(andisMergedCasesOne, andisMergedCasesTwo));
            return cb.and(adds.toArray(new Predicate[]{}));
        });
        //如果检索到数据说明排期冲突
        if (!(caseSchedulings.isEmpty())){
            ConflictScheduling conflicscheduling=new ConflictScheduling();
            BeanUtils.copyProperties(caseScheduling,conflicscheduling);
            conflicschedulingRepository.save(conflicscheduling);
            CaseScheduling caseSchedulingConflics = caseSchedulings.get(0);
            throw new ServerException("排期时间冲突, 冲突的案件为:"+caseSchedulingConflics.getCaseNo()+"," +
                    "冲突的时间段为("+caseSchedulingConflics.getStartTime()+"~"+caseSchedulingConflics.getEndTime()+")");
        }

    }

9.8 条件排序

 List<Sort.Order> orders=new ArrayList<>();
        //开庭中优先级最高 ->未开庭 -> 开庭中最后
        orders.add(new Sort.Order(Sort.Direction.DESC,"isOpen"));
        // 从前到后
        orders.add(new Sort.Order(Sort.Direction.ASC,"startTime"));
        Pageable pageable = PageRequest.of(page, size,Sort.by(orders));

9.9 模糊查询(LIKE)

为了方便起见,service直接忽略,方便理解。

9.9.1 方法一使用自带方法

  1. Controller层:

方法参数如下,一定要加 “%”+name+“%”

@RestController
public class UserController {
 
    @Autowired
    private TeamRepository teamRepository;
 
    @GetMapping("/findByNameLike")
    public List<Team> findByNameLike(String name) {
        // 一定要加 "%"+参数名+"%"
        return teamRepository.findByNameLike("%"+name+"%");
    }
 
}
  1. Dao层:

一定要使用 JPA 规定的形式 findBy+参数名+Like(参数)


public interface TeamRepository extends JpaRepository<Team, String> {
 
    
    List<Team> findByNameLike(String name);

9.9.2 方法二使用sql

  1. Controller:

参数简单化

/**
 * @description:
 * @author: czx<15610554031@163.com>
 * @date: 2018/1/22 下午5:15
 * @version: V1.0
 */
@RestController
public class UserController {
 
    @Autowired
    private TeamRepository teamRepository;
 
    @GetMapping("/findByNameLike")
    public List<Team> findByNameLike(String name) {
        return teamRepository.findByNameLike(name);
    }
 
}

  1. Dao层:

需要自己定义SQL语句

/**
 * @description: 
 * @author: czx<15610554031@163.com>
 * @date: 2018/1/18 上午10:52
 * @version: V1.0
 */
 
public interface TeamRepository extends JpaRepository<Team, String> {
 
    @Query(value = "select t from Team t where t.name like %?1%")
    List<Team> findByNameLike(String name);

9.10 JPA自定义sql实现分页查询

CustomerRepository.java

@Query(nativeQuery = true, value = "select * from adm_sys_customer ORDER BY ?#{#pageable}",

countQuery = "select count(*) from adm_sys_customer")

Page<Customer> findAllByPageable(Pageable pageable);

CustomerServiceImpl.java

//调用自定义sql



Page<Customer> pageObjectList = customerRepos.findAllByPageable(pageService.getPageable(1, 1, null));

ORDER BY ?#{#pageable} 必须加,否则报错如果采用本地(nativeQuery = true)可以不加:

Caused by: org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException:

Cannot use native queries with dynamic sorting and/or pagination in …

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赵广陆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值