JPA & Spring Data JPA详解

JPA & Spring Data JPA

一、JPA

1. JPA是什么

JPA(Java Persistence API)Java持久化 API,是一套基于ORM思想的规范。

ORM(Object Relational Mapping)对象关系映射。实现了实体类对象和数据库中表的一个映射关系。我们可以通过操作实体类对象(JavaBean)来操作数据库表,从而实现数据库的CRUD操作并且不需要去重点关注SQL语句。ORM主要涉及到两个映射关系:1、实体类和表的映射关系;2、实体类中的属性和表中的字段的映射关系。像Mybatis框架:一个不完全的ORM框架,需要开发人员写一部分的SQL语句;Hibernate框架:是一个完全的ORM框架,需要编写SQL语句

规范:顾名思义就是只定义了而没有实现,因此可以明白JPA的内部就是由一系列的接口和抽象类组成,自身并没有去实现。

综上所述JPA就是众多实现了ORM思想框架的一个规范,内部只有接口和抽象类,没有实现类。(面向接口编程)

2. 为什么用

2.1 标准化

JPA是一个规范,这就说明任何实现了 JPA 规范的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。

2.2 容器级特性的支持

JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。

2.3 简单方便

JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成

2.4 查询能力

JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。

2.5 高级特性

JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。

3. 怎么用

3.1 JPA的实现

实现JPA规范的常见的框架有Hibernate、TopLink和OpenJPA. 以Hibernate为例。
在这里插入图片描述
我们在使用JPA操作数据库时,其底层还是由Hibernate去实现的

3.2 环境搭建

3.2.1 导入座标
 <dependencies>

        <!--hibernate对jpa的支持-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.10.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>5.4.10.Final</version>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

3.2.2 创建配置文件persistence.xml

注:persistence.xml必须放在类路径下的META-INF文件下

<?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">

    <!--配置持久化单元
        name:持久化单元名称
      transaction-type:事务类型
         RESOURCE_LOCAL:本地事务管理
         JTA:分布式事务管理
    -->
    <persistence-unit name="myJPA" transaction-type="RESOURCE_LOCAL">

        <!--配置JPA规范的服务提供商-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!--配置数据源-->
        <properties>
            <!-- 数据库信息
                用户名,javax.persistence.jdbc.user
                密码,  javax.persistence.jdbc.password
                驱动,  javax.persistence.jdbc.driver
                数据库地址   javax.persistence.jdbc.url
            -->

            <!-- 标准配置方法,适用性高 -->
            <!--<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/jpa"/>
            <property name="javax.persistence.jdbc.username" value="root"/>
            <property name="javax.persistence.jdbc.password" value="123456"/>-->


            <!-- hibernate 的配置方法-->
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.url" value="jdbc:mysql:///jpa?characterEncoding=UTF-8"></property>
            <property name="hibernate.connection.username" value="root"></property>
            <property name="hibernate.connection.password" value="123456"></property>

            <!--配置jpa实现方(hibernate)的配置信息
                 显示sql           :   false|true
                 自动创建数据库表    :  hibernate.hbm2ddl.auto
                         create      : 程序运行时创建数据库表(如果有表,先删除表再创建)
                         update      :程序运行时创建表(如果有表,不会创建表)
                         none        :不会创建表

             -->
            
            <!--配置方言-->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"></property>
            
            <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>
3.2.3 编写实体类
3.2.4 配置实体类和数据库表、属性和表中字段的映射关系
Entity   //声明这是一个实体类
@Table(name = "cst_customer") //实体类和数据库表的映射
public class Customer implements Serializable {

    private static final long serialVersionUID = 2845630796773320819L;

    /**
     * @Id:声明主键的配置
     * @GeneratedValue:配置主键的生成策略
     *      strategy
     *          GenerationType.IDENTITY :自增,mysql
     *                 * 底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
     *          GenerationType.SEQUENCE : 序列,oracle
     *                  * 底层数据库必须支持序列
     *          GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
     *          GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略
     * @Column:配置属性和字段的映射关系
     *      name:数据库表中字段的名称
     */

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Integer custId;

    @Column(name = "cust_name")
    private String custName;

    @Column(name = "cust_source")
    private String custSource;

    @Column(name = "cust_industry")
    private String custIndustry;

    @Column(name = "cust_level")
    private String custLevel;

    @Column(name = "cust_address")
    private String custAddress;

    @Column(name = "cust_phone")
    private String custPhone;
    
    //get | set | toString
    .............
}

3.3 JPA操作数据库的流程

3.3.1 加载总配置文件persistence.xml并且创建实体管理器工厂(EntityManagerFactory)
3.3.2 根据实体管理器工厂创建实体管理器(EntityManager)

问题:EntityManagerFactory内部维护了很多信息并且是线程安全的对象,所以创建的过程非常的浪费资源且耗时

解决办法:利用静态代码块在类加载的时候创建一个公共的EntityManagerFactory对象

注:多个线程访问同一个EntityManagerFactory对象不会造成线程安全的问题

public class JpaUtils {

    private static EntityManagerFactory factory;

    static {
        //创建一个公共的EntityManagerFactory
        factory = Persistence.createEntityManagerFactory("myJPA");
    }

    /**
     * 获取EntityManager
     */
    public static EntityManager getEntityManager(){
        return factory.createEntityManager();
    }
}
3.3.3 根据实体管理器获取事务对象并开启事务

获取事务对象:getTransaction()

开启事务:begin()

3.3.4 进行增删改查操作

根据id查询:find() | getRefrence()

/**
 * find | getRefrence : 根据id查询数据
 *      class:查询数据的结果需要包装的实体类类型的字节码
 *      id:查询的主键的取值
 *
 * 不同点:find 立即加载
 *        getRefrence 延迟加载
 */
Customer customer = entityManager.find(Customer.class, 1);
//Customer customer = entityManager.getReference(Customer.class, 1);
System.out.println(customer);


插入数据:persist()

更新数据:merge()

//先获取数据,然后更新
Customer customer = entityManager.find(Customer.class, 1);
customer.setCustName("小王");
entityManager.merge(customer);

删除数据:remove()

//先获取数据,然后删除
Customer customer = entityManager.find(Customer.class, 1);
entityManager.remove(customer);
3.3.5 提交事务、释放资源

流程示例:

//加载配置文件并且创建EntityManagerFactory
//根据EntityManagerFactory创建EntityManager
EntityManager entityManager = JpaUtils.getEntityManager();

//获取事务对象并开启事务
EntityTransaction tx = entityManager.getTransaction();
tx.begin();

//进行增删改查操作,以插入数据为例
Customer customer = new Customer();
customer.setCustName("王五");
customer.setCustIndustry("计算机");
entityManager.persist(customer);  //保存操作

//提交事务、释放资源
tx.commit();
entityManager.close();

3.4 复杂查询

JPQL(Java Persistence Query Language):Java持久化查询语言

特点:面向对象、关键字和SQL语句一样

规则: 1. 里面不能出现表名,列名,只能出现java的类名,属性名,区分大小写

​ 2. 关键字不区分大小写

​ 3. 不能写select *

查询的步骤:

1.EntityManager创建Query对象

2.如果包含参数,setParameter()

3.如果需要分页,调用Query的setFirstResult()或者setMaxResult()

4.如果是select语句,使用getResultList()或者getSingleResult();

常见的方法:

//用于select语句
List getResultList() 
Object getSingleResult()

//用于update,delete语句
int executeUpdate()

//分页查询时参数的设置
Query setFirstResult(int startPosition)
Query setMaxResult(int maxResult)
    
//设置参数
setParameter()
    
3.4.1 查询全部
//创建实体管理器对象,开启事务
.......
   
//查询操作
    
//SQL语句:select * from cst_customer
String jpql = "from Customer"; 

//创建query对象(query对象才是真正的执行jpql的对象)
Query query = entityManager.createQuery(jpql);

List list = query.getResultList();

for (Object customers : list) {
    System.out.println(customers);
}

//提交事务、关闭对象
3.4.2 分页查询
/**
     * 分页查询
     * sql:select * from customer limit ?,?
     * jpql: from Customer
     */

String jpql = "from Customer";

Query query = entityManager.createQuery(jpql);

//设置分页参数
query.setFirstResult(0);
query.setMaxResults(2);

List list = query.getResultList();

for (Object customers : list) {
    System.out.println(customers);
}
3.4.3 统计查询
/**
     * 统计查询
     * sql:select count(cust_id) from customer
     * jpql: select count(custId) from Customer
     */

String jpql = "select count(custId) from Customer";

Query query = entityManager.createQuery(jpql);

Object result = query.getSingleResult();

System.out.println(result);
3.4.4 条件查询
/**
 * 条件查询
 * sql:select * from customer where cust_name like ?
 * jpql: from Customer where custName like ?1
 */

 String jpql = "from Customer where custName like ?1";

 Query query = entityManager.createQuery(jpql);

 //设置参数
 query.setParameter(1,"li%");

 List list = query.getResultList();

 for (Object customers : list) {
    System.out.println(customers);
 }
3.4.5 排序查询
/**
     * 查询所有并实现倒序的功能
     * sql:select * from customer order by cust_id desc
     * jpql: from Customer order by custId desc
     */

String jpql = "from Customer order by custId desc";

Query query = entityManager.createQuery(jpql);

List list = query.getResultList();

for (Object customers : list) {
    System.out.println(customers);
}

二、Spring Data JPA

1. 是什么

1.1 Spring Data的概述

在这里插入图片描述

Spring Data 是Spring的一个子项目,主要用来简化数据库的访问,支持关系型数据库和NoSQL,并且支持云服务。

1.2 Spring Data JPA的概述

Spring Data JPA 是 Spring Data的一个子模块,致力于减少数据访问层(DAO)的开发量,它可以极大的简化JPA的写法,开发者只需要声明持久层的接口就可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。

2. 怎么用

2.1 Spring Data JPA的实现过程

在这里插入图片描述

2.2 Spring Data JPA的核心接口

2.2.1 Repository

最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别

在这里插入图片描述

2.2.2 CrudRepository

是Repository的子接口,提供CRUD的功能

在这里插入图片描述

2.2.3 PageAndSortingRepository

是CrudRepository的子接口,添加分页和排序的功能

在这里插入图片描述

2.2.4 JpaRepository

是PagingAndSortingRepository的子接口,实现一组 JPA 规范相关的方法。凡是自定义的持久层的接口,一般都要继承这个接口

2.2.5 JpaSpecificationExecutor

用来做复杂查询的接口

在这里插入图片描述

2.2.6 Specification

是Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可

2.3 开发步骤

2.3.1 导入Spring Data JPA 的相关依赖
 <!--spring-data-jpa的相关依赖-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

        <!-- el  使用spring data jpa 必须引入 -->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>3.0.0</version>
        </dependency>
2.3.2 整合spring 和 Spring Data 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"
       xmlns:task="http://www.springframework.org/schema/task"
       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">

    <!--spring的相关配置-->
    <!--1.配置spring容器启动是要扫描的包-->
    <context:component-scan base-package="com.springdata"></context:component-scan>

    <!--Spring Data JPA相关的配置-->
    <!--1.创建entityManagerFactory对象交给spring容器管理-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"></property>

        <!--配置要扫描的包(实体类所在的包)-->
        <property name="packagesToScan" value="com.springdata.bean"></property>

        <!--jpa的实现厂商-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
        </property>

        <!--jpa的供应商适配器-->
        <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" />
                <!--是否显示sql -->
                <property name="showSql" value="true" />
            </bean>
        </property>

        <!--jpa的方言 :高级的特性 -->
        <property name="jpaDialect" >
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        </property>
                  
    </bean>

    <!--2.数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa?characterEncoding=utf-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--spring整合spring data jpa-->
    <jpa:repositories base-package="com.springdata.dao" transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactory">
    </jpa:repositories>

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

</beans>
2.3.3 编写实体类
2.3.4 声明持久层的接口

需要继承两个接口:JpaRepository 、JpaSpecificationExecutor

/**
 * 符合SpringDataJpa的dao层接口规范
 *      JpaRepository<操作的实体类类型,实体类中主键属性的类型>
 *          * 封装了基本CRUD操作
 *      JpaSpecificationExecutor<操作的实体类类型>
 *          * 封装了复杂查询(分页)
 */
public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {

}

2.4 Spring Data JPA的查询方式

2.4.1 使用Spring Data JPA中接口定义的方法进行查询

注:不需要在持久层接口中写任何方法

 //持久层接口的代码
public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {

}

//测试类中的方法
/**
     * 根据id查询  findById:立即加载  getOne:延迟加载
     */
    @Test
    public void testFindById(){
        /*Optional<Customer> customer = customerDao.findById(1);
        if (customer.isPresent()){
            System.out.println(customer.get());
        }else{
            System.out.println("你要找的值不存在");
        }*/
        Customer customer1 = customerDao.findById(1).orElse(null);
        System.out.println(customer1);

    }

JpaRepository接口中的方法列表:

在这里插入图片描述

JpaSpecificationExecutor接口中的方法列表:

在这里插入图片描述

2.4.2 使用 JPQL的查询方式

需要将JPQL语句配置到接口方法上

  1. 需要在dao接口上配置方法
  2. 在新添加的方法上,使用注解的形式配置jpql查询语句
  3. 注解 : @Query
public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {

    /**
     * 使用jpql语句查询
     * 根据custName和custId查询用户的信息
     * jpql语句:from Customer where custName = ?1 and custId = ?2
     * ?1,?2代表参数的占位符,其中1,2对应方法中的参数索引
     */
    @Query("from Customer where custName = ?1 and custId = ?2")
    public Customer findCustByNameId(String custName, Integer custId);

    /**
     * 使用jpql实现更新操作: @Query、@Modifying
     * jpql: update Customer set custName = ?1 where custId = ?2
     *
     */
    @Query("update Customer set custName = ?1 where custId = ?2")
    @Modifying
    public void updateCustomer(String custName, Integer custId);
}

注:使用JPQL更新时,需要添加事务的支持

/**
     * 使用jpql实现更新操作
     * springDataJpa中使用jpql完成 更新/删除操作
     *      需要手动添加事务的支持
     *      默认会执行结束之后,回滚事务
     *      Rollback : 设置是否自动回滚
     *         false | true
     */
    @Test
    @Transactional
    @Rollback(value = false)
    public void testUpdateCust(){
        customerDao.updateCustomer("李四",2);
    }
2.4.3 使用SQL语句查询

需要将SQL语句配置到接口方法上

  1. 需要在dao接口上配置方法

  2. 在新添加的方法上,使用注解的形式配置SQL查询语句

  3. 注解 : @Query

    ​ 属性:nativeQuery :true(SQL)| false(JPQL)

public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {
    
    /**
     * 使用sql语句查询
     * nativeQuery: false(jpql查询)|true(使用本地查询:sql查询)
     */
    @Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery = true)
    public List<Customer> findCustByName(String custName);

}
2.4.4 使用方法命名规则查询

方法名要按照指定的命名规则来命名

public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {
    /**
     * 根据方法命名规则查询
     *
     * 方法名的约定:
     *      findBy : 查询
     *            对象中的属性名(首字母大写) : 查询的条件
     *
     *  findByCustName   --   根据客户名称查询
     *
     *  在springdataJpa的运行阶段
     *          会根据方法名称进行解析  findBy    from  xxx(实体类)
     *                                      属性名称      where  custName =
     *
     *      1.findBy  + 属性名称 (根据属性名称进行完成匹配的查询=)
     *      2.findBy  + 属性名称 + “查询方式(Like | isnull)”
     *          findByCustNameLike
     *      3.多条件查询
     *          findBy + 属性名 + “查询方式”   + “多条件的连接符(and|or)”  + 属性名 + “查询方式”
     */

    /**
     * 根据客户名精准查询
     */
    public Customer findByCustName(String custName);


    /**
     *使用客户名称查询
     */
    public List<Customer> findByCustNameLike(String custName);

    /**
     * 使用客户名称模糊匹配和客户所属行业精准匹配的查询
     */
    public Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);
}

常见的关键字对应的方法名称命名规则

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
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)

2.5 Spring Data JPA 的运行过程原理剖析

以findById()为例

  1. 通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象SimJpaRepository

在这里插入图片描述

  1. SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成数据库的CRUD)

在这里插入图片描述

  1. 通过hibernate完成数据库操作(封装了jdbc)

点开上图中的find方法,如下图所示

在这里插入图片描述

2.6 Specifications动态查询

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

public interface JpaSpecificationExecutor<T> {
    
    //根据条件查询单个对象
    Optional<T> findOne(@Nullable Specification<T> var1);

    //根据条件查询全部
    List<T> findAll(@Nullable Specification<T> var1);

    //根据条件查询全部,并未分页,返回的是一个pageBean对象
    //Pageable:分页参数
    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

    //查询并排序
    //Sort:排序的参数
    List<T> findAll(@Nullable Specification<T> var1, Sort var2);

    //统计查询
    long count(@Nullable Specification<T> var1);
}

对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。

public interface Specification<T> extends Serializable {
     
    /**
    *	root:查询的根对象(查询的任何属性都可以从根对象中获取)
    *	CriteriaQuery:代表一个顶层查询对象,用来自定义查询
    *	CriteriaBuilder:用来构建查询,此对象里有很多条件方法
    */
    
    //封装查询条件
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}



/**
     * 自定义查询条件
     *      1.实现Specification接口(提供泛型:查询的对象类型)
     *      2.实现toPredicate方法(构造查询条件)
     *      3.需要借助方法参数中的两个参数(
     *          root:获取需要查询的对象属性
     *          CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)
     *       )
     */
2.6.1 单条件查询
/**
     * 单条件查询
     * 案例:根据客户名称查询,查询客户名为王五的客户
     *       查询条件
     *           1.查询方式
     *               cb对象
     *           2.比较的属性名称
     *               root对象
     */
    @Test
    public void findOneTest(){

        Specification<Customer> spec = new Specification() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1.获取比较的属性
                Path custName = root.get("custName");
                
                //2.构造查询
                /**
                 * 查询方式
                 * 第一个参数:需要比较的属性(path对象)
                 * 第二个参数:当前需要比较的取值
                 */

                Predicate predicate = criteriaBuilder.equal(custName, "王五");
                return predicate;
            }
        };
        Optional<Customer> customer = customerDao.findOne(spec);
        System.out.println(customer);

    }
2.6.2 多条件查询
/**
     * 多条件查询
     * 根据客户名称和行业查询
     */
    @Test
    public void findAllSpecTest(){
        Specification<Customer> spec = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //获取比较的属性
                Path<Object> custName = root.get("custName");
                Path<Object> custIndustry = root.get("custIndustry");

                //构造查询
                //1.构造客户名的精准匹配查询
                Predicate predicate1 = criteriaBuilder.equal(custName, "lisi");
                //2..构造所属行业的精准匹配查询
                Predicate predicate2 = criteriaBuilder.equal(custIndustry, "education");
                //3.将多个查询条件组合到一起:组合(满足条件一并且满足条件二:与关系,满足条件一或满足条件二即可:或关系)
                Predicate predicate = criteriaBuilder.and(predicate1, predicate2);

                return predicate;
            }
        };
        List<Customer> customers = customerDao.findAll(spec);
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }
2.6.3 模糊查询

常见的方法名称与SQL之间的对应关系

方法名称 Sql对应关系
equle 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
 /**
     * 模糊查询
     * 根据客户名称完成模糊查询
     * gt,lt,ge,le,like : 得到path对象,根据path指定比较的参数类型,再去进行比较
     * 指定参数类型:path.as(类型的字节码对象)
     */
    @Test
    public void findBySpecTest(){
        Specification<Customer> spec = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1. 获取比价的属性
                Path<Object> custName = root.get("custName");

                //构造查询
                Predicate predicate = criteriaBuilder.like(custName.as(String.class), "li%");
                return predicate;
            }
        };
        List<Customer> customers = customerDao.findAll(spec);
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }
2.6.4 分页查询
/**
     * 分页查询
     */
    @Test
    public void findAllPageableTest(){

        //根据条件查询出相应的所有数据
        Specification<Customer> spec = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1. 获取比价的属性
                Path<Object> custName = root.get("custName");

                //构造查询
                Predicate predicate = criteriaBuilder.like(custName.as(String.class), "li%");
                return predicate;
            }
        };

        //定义分页参数
        //PageRequest对象是Pageable接口的实现类
        //第一个参数:指定开始查询的页码 0表示第一页
        //第二个参数:指定每页大大小
        Pageable pageable = PageRequest.of(0,2);
        Page<Customer> customerPage = customerDao.findAll(spec,pageable);

        //获取所有数据的集合列表
        List<Customer> customers = customerPage.getContent();
        for (Customer customer : customers) {
            System.out.println(customer);
        }

        //获取总共的元素数(所有数据的个数)
        System.out.println(customerPage.getTotalElements());

        //获取总共的页数
        System.out.println(customerPage.getTotalPages());
    }
2.6.5 排序查询
 /**
     * 查询并排序
     */
    @Test
    public void findAllSpecSortTest(){
        //根据条件查询出相应的所有数据
        Specification<Customer> spec = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1. 获取比价的属性
                Path<Object> custName = root.get("custName");

                //构造查询
                Predicate predicate = criteriaBuilder.like(custName.as(String.class), "li%");
                return predicate;
            }
        };

        //定义排序的参数
        //根据id倒叙
        Sort sort = Sort.by(Sort.Direction.DESC,"custId");
        List<Customer> customers = customerDao.findAll(spec, sort);
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }

2.7 多表操作

多表之间的关系有三种:

  1. 一对一

  2. 一对多:需要在从表上添加主表的主键信息作为外键

    ​ 一的一方:主表

    ​ 多的一方:从表

  3. 多对多

    ​ 中间表:中间表中最少应该由两个字段组成,这两个字段做为外键指向两张表的主键,又组成了联合主键

2.7.1 一对多

主要注解:

@OneToMany:

作用:建立一对多的关系映射

属性:

​ targetEntityClass:对方对象的字节码对象

​ mappedBy:对方配置关系的属性名称。

​ cascade:指定要使用的级联操作

​ fetch:指定是否采用延迟加载

​ orphanRemoval:是否使用孤儿删除

@ManyToOne

作用:建立多对一的关系

属性:

​ targetEntityClass:对方对象的字节码对象

​ cascade:指定要使用的级联操作

​ fetch:指定是否采用延迟加载或立即加载

​ optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

@JoinColumn

作用:配置外键

属性:

​ name:指定外键字段的名称

​ referencedColumnName:指定引用主表的主键字段名称

​ unique:是否唯一。默认值不唯一

​ nullable:是否允许为空。默认值允许。

​ insertable:是否允许插入。默认值允许。

​ updatable:是否允许更新。默认值允许。

​ columnDefinition:列的定义信息。

 //Customer.java  主表的实体类

/**
     *客户对联系人是一对多的关系
     */
//    @OneToMany(targetEntity = LinkMan.class)
//    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")

    /**
     * 放弃外键维护权
     * mappedBy = "对方配置关系的属性名称"
     *
     * cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
     *     CascadeType.all: 所有
     *               MERGE: 更新
     *             PERSIST: 保存
     *              REMOVE: 删除
     */
    @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
    private Set<LinkMan> linkMen = new HashSet<>();


//LinkMan.java  从表的实体类
/**
     * 联系人对客户是多对一的关系
     */
    @ManyToOne(targetEntity = Customer.class)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer;
2.7.2 多对多

@ManyToMany

​ 作用:用于映射多对多关系

​ 属性:

​ cascade:配置级联操作

​ fetch:配置是否采用延迟加载或立即加载

​ FetchType.EAGER:立即加载

​ FetchType.LAZY:延迟加载

​ targetEntity:配置目标的实体类

@JoinTable

作用:针对中间表的配置(外键)

属性:

​ nam:配置中间表的名称

​ joinColumns:配置当前实体类所对应表的主键字段

​ inverseJoinColumn:配置对方表的主键字段

  //User.Java

@ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
    @JoinTable(name = "sys_user_role",
            //joinColumns,当前对象在中间表中的外键
            joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
            //inverseJoinColumns,对方对象在中间表的外键
            inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
    )
    private Set<Role> roles = new HashSet<>();




   //Role.Java
    //被动的一方放弃外键的维护权
    @ManyToMany(mappedBy = "roles")  //配置多表关系
    private Set<User> users = new HashSet<>();

2.7.3 对象导航查询

对象导航查询:查询一个对象的同时,通过此对象查询他的关联对象

以一对多为例:

​ 从一方关联查询多方:默认采用延迟加载

​ 从多方关联查询一方:默认采用立即加载# 欢迎使用Markdown编辑器

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章