Spring Data Jpa使用篇

author:尹会东start
createTime:2020-06-04

文章目录

一,orm思想和hibernate以及jpa

1.orm思想

在这里插入图片描述

主要目的:操作实体类就相当于操作数据库表

建立两个映射关系

实体类

实体类中的属性表中字段的映射关系

不再重点关注: SQL语句

实现了ORM思想的框架:mybatishibernate

2. hibernate框架介绍

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

3.jpa规范

在这里插入图片描述

JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。

JPA通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

JPA与hibernate的关系

在这里插入图片描述

JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。JPA怎么取代Hibernate呢?JDBC规范可以驱动底层数据库吗?答案是否定的,也就是说,如果使用JPA规范进行数据库操作,底层需要hibernate作为其实现类完成数据持久化工作。

4.jpa的入门案例

1.案例:是客户的相关操作(增删改查)

​ 客户:就是一家公司

2.创建项目导入依赖

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.hibernate.version>5.0.7.Final</project.hibernate.version>
    </properties>

    <dependencies>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!-- hibernate对jpa的支持包 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${project.hibernate.version}</version>
        </dependency>

        <!-- c3p0 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>${project.hibernate.version}</version>
        </dependency>

        <!-- log日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!-- Mysql and MariaDB -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>

3.配置文件

位置:配置到类路径下的一个叫做 META-INF 的文件夹下
命名:persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 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>
            <!-- 数据库驱动 -->
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <!-- 数据库地址 -->
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa"/>
            <!-- 数据库用户名 -->
            <property name="javax.persistence.jdbc.user" value="root"/>
            <!-- 数据库密码 -->
            <property name="javax.persistence.jdbc.password" value="root"/>

            <!--jpa提供者的可选配置:我们的JPA规范的提供者为hibernate,所以jpa的核心配置中兼容hibernate的配 -->
            <!--显示sql语句-->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <!--
                建表方式
                create:每次执行都创建表
                update:没有表才创建
                none:不创建表
            -->
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
</persistence>

4.实体类

/**
 * @author yinhuidong
 * @createTime 2020-06-01-21:43
 */
//作用:指定当前类是实体类。
@Entity
/**
 * 作用:指定实体类和表之间的对应关系。
 * 属性:
 *   name:指定数据库表的名称
 */
@Table(name = "t_person")
public class Person {
    //作用:指定当前字段是主键。
    /**
     * @Id
     *     作用:指定主键的生成方式。。
     *     属性:
     *     strategy :指定主键生成策略。
     */
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    /**
     * @Column
     * 作用:指定实体类属性和数据库表之间的对应关系
     * 属性:
     *      name:指定数据库表的列名称。
     *      unique:是否唯一  
     *      nullable:是否可以为空  
     *      inserttable:是否可以插入  
     *      updateable:是否可以更新  
     *      columnDefinition: 定义建表时创建此列的DDL  
     *      secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),
     *      该属性定义该列所在从表的名字搭建开发环境[重点]
     */
    @Column(name = "id")
    private Integer id;
    @Column(name = "p_name")
    private String name;
    @Column(name = "p_age")
    private Integer age;
    @Column(name = "p_email")
    private String email;
    @Column(name = "p_birth")
    private Date birth;

    public Person() {
    }

    public Person(Integer id, String name, Integer age, String email, Date birth) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
        this.birth = birth;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

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

    public String getEmail() {
        return email;
    }

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

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", birth=" + birth +
                '}';
    }
}

5.测试类

jpa操作的操作步骤

1.加载配置文件创建实体管理器工厂
    Persisitence:静态方法(根据持久化单元名称创建实体管理器工厂)
        createEntityMnagerFactory(持久化单元名称)
    作用:创建实体管理器工厂

2.根据实体管理器工厂,创建实体管理器
    EntityManagerFactory :获取EntityManager对象
    方法:createEntityManager
    * 内部维护的很多的内容
        内部维护了数据库信息,
        维护了缓存信息
        维护了所有的实体管理器对象
        再创建EntityManagerFactory的过程中会根据配置创建数据库表
    * EntityManagerFactory的创建过程比较浪费资源
    特点:线程安全的对象
        多个线程访问同一个EntityManagerFactory不会有线程安全问题
    * 如何解决EntityManagerFactory的创建过程浪费资源(耗时)的问题?
    思路:创建一个公共的EntityManagerFactory的对象
    * 静态代码块的形式创建EntityManagerFactory

3.创建事务对象,开启事务
    EntityManager对象:实体类管理器
        beginTransaction : 创建事务对象
        presist : 保存
        merge  : 更新
        remove : 删除
        find/getRefrence : 根据id查询

    Transaction 对象 : 事务
        begin:开启事务
        commit:提交事务
        rollback:回滚
4.增删改查操作
5.提交事务
6.释放资源

测试

    @Test
    public void testSave(){
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
        EntityManager em = factory.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        Person person = new Person(null,"aaa",22,"[email protected]",new Date());
        em.persist(person);
        tx.commit();
    }

5.主键生成策略

@GeneratedValue:配置主键的生成策略
strategy
GenerationType.IDENTITY :自增,mysql
底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
GenerationType.SEQUENCE : 序列,oracle
底层数据库必须支持序列
GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略

6.JPA的API介绍

1.Persistence对象

Persistence对象主要作用是用于获取EntityManagerFactory对象的 。通过调用该类的createEntityManagerFactory静态方法,根据配置文件中持久化单元名称创建EntityManagerFactory

EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");

2.EntityManagerFactory

EntityManagerFactory 接口主要用来创建 EntityManager 实例

EntityManager em = factory.createEntityManager();

由于EntityManagerFactory 是一个线程安全的对象(即多个线程访问同一个EntityManagerFactory 对象不会有线程安全问题),并且EntityManagerFactory 的创建极其浪费资源,所以在使用JPA编程时,我们可以对EntityManagerFactory 的创建进行优化,只需要做到一个工程只存在一个EntityManagerFactory 即可。

3. EntityManager

在 JPA 规范中,EntityManager是完成持久化操作的核心对象。实体类作为普通 java对象,只有在调用 EntityManager将其持久化后才会变成持久化对象。EntityManager对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。我们可以通过调用EntityManager的方法完成获取事务,以及持久化数据库的操作

	getTransaction : 获取事务对象
	persist : 保存操作
	merge : 更新操作
	remove : 删除操作
	find/getReference : 根据id查询

4.EntityTransaction

在 JPA 规范中, EntityTransaction是完成事务操作的核心对象,对于EntityTransaction在我们的java代码中承接的功能比较简单

begin:开启事务
commit:提交事务
rollback:回滚事务

7. 抽取JPAUtil工具类

/**
 * @author yinhuidong
 * @createTime 2020-06-02-0:13
 */
public class JpaUtils {


    private static EntityManagerFactory factory;
    static {
        factory = Persistence.createEntityManagerFactory("myJpa");
        
    }

    public static EntityManager getEm(){
        return factory.createEntityManager();
    }
}

8. 使用JPA完成增删改查操作

1.保存

    /**
     * 添加
     */
    @Test
    public void testSave(){

        EntityManager em = JpaUtils.getEm();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        Person person = new Person(null,"aaa",22,"[email protected]",new Date());
        em.persist(person);
        tx.commit();
        em.close();
    }

2.删除

   /**
     * 删除:remove
     */
    @Test
    public void testRemove(){
        EntityManager em = JpaUtils.getEm();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        Person person = em.getReference(Person.class, 1);
        em.remove(person);
        tx.commit();
        em.close();
    }

3.更新

    /**
     * 更新:merge
     */
    @Test
    public void testmerge(){
        EntityManager em = JpaUtils.getEm();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        Person person = new Person(1,"bbb",22,"[email protected]",new Date());
        em.merge(person);
        tx.commit();
        em.close();
    }

4.根据ID查询

1)立即加载

    /**
     * 查询:find
     * 立即加载,执行find()立刻发出sql
     */
    @Test
    public void testFind(){
        EntityManager em = JpaUtils.getEm();
        Person person = em.find(Person.class, 1);
        System.out.println(person);
        em.close();
    }

2)懒加载

    /**
     * 查询:getReference
     * 懒加载机制,什么时候调用查询结果什么时候发出sql语句
     * 得到的是一个动态代理对象
     */
    @Test
    public void testgetReference(){
        EntityManager em = JpaUtils.getEm();
        Person person = em.getReference(Person.class, 1);
        System.out.println(person);
        em.close();
    }

5.复杂查询

JPQL全称Java Persistence Query Language

Java持久化查询语言(JPQL)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起·使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。

其特征与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性

1)查询全部

    /**
     * 查询所有
     */
    @Test
    public void test1(){
        EntityManager em = JpaUtils.getEm();
        String jpql ="from Person";
        Query query = em.createQuery(jpql);
        query.getResultList().forEach(System.out::println);
        em.close();
    }

2)分页查询

    /**
     * 分页查询
     */
    @Test
    public void test1(){
        EntityManager em = JpaUtils.getEm();
        String jpql ="from Person";
        Query query = em.createQuery(jpql);
        query.setFirstResult(0);
        query.setMaxResults(2);
        query.getResultList().forEach(System.out::println);
        em.close();
    }

3)条件查询

    /**
     * 条件查询
     */
    @Test
    public void test1(){
        EntityManager em = JpaUtils.getEm();
        String jpql ="from Person where name like ?";
        Query query = em.createQuery(jpql);
        query.setParameter(1,"aa%");
        //获取唯一结果集
        //System.out.println(query.getSingleResult());
        query.getResultList().forEach(System.out::println);
        em.close();
    }

4)排序查询

    /**
     * 排序查询
     */
    @Test
    public void test1(){
        EntityManager em = JpaUtils.getEm();
        String jpql ="from Person order by id desc";
        Query query = em.createQuery(jpql);
        query.getResultList().forEach(System.out::println);
        em.close();
    }

5)统计查询

    /**
     * 统计查询
     */
    @Test
    public void test1(){
        EntityManager em = JpaUtils.getEm();
        String jpql ="select count(id) from Person";
        Query query = em.createQuery(jpql);
        System.out.println(query.getSingleResult());
        em.close();
    }

二,springdatajpa的运行原理以及基本操作

1.springDataJpa概述

1.1SpringDataJpa概述

Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!

Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦

1.2springdatajpa的特性

SpringData Jpa 极大简化了数据库访问层代码。 如何简化的呢? 使用了SpringDataJpa,我们的dao层中只需要写接口,就自动具有了增删改查、分页查询等方法。

1.3Spring Data JPA 与 JPA和hibernate之间的关系

JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)

Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。

2.SpringDataJpa快速入门

2.1需求说明

Spring Data JPA完成客户的基本CRUD操作

2.2搭建环境

项目依赖

        <!--hibernate-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.15.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.0.7.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.3.6.Final</version>
        </dependency>
        <!--springdatajpa-->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.3.0.RELEASE</version>
        </dependency>

Spring整合SpringDataJPA

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

<!--组件扫描-->
    <context:component-scan base-package="com.example">
        <!-- 配置要忽略的注解 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!--Spring整合MyBatis-->
    <!--配置数据源-->
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.DriverName}"/>
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <jpa:repositories base-package="com.example.mapper"
                      transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
    <!--事务管理-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="my" expression="execution(* com.example.service.impl.*.* (..))"></aop:pointcut>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="my"></aop:advisor>
    </aop:config>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="packagesToScan" value="com.example.domain"></property>
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>
        <!--jpa的供应商适配器-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="false"></property>
                <property name="database" value="MYSQL"></property>
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"></property>
                <property name="showSql" value="true"></property>
            </bean>
        </property>
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean>
        </property>
    </bean>
</beans>

关联映射实体类

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

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id")
    private Long id;
    @Column(name = "p_name")
    private String name;
    @Column(name = "p_age")
    private Integer age;
    @Column(name = "p_email")
    private String email;
    @Column(name = "p_birth")
    private Date birth;
}

mapper接口

/**
 * @author yinhuidong
 * @createTime 2020-06-04-15:46
 * JpaRepository<Person, Long> 用来完成基本CRUD
 * JpaSpecificationExecutor<Person> 用来完成复杂查询
 */
public interface PersonMapper extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {
}

测试类

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

    @Autowired
    private PersonMapper mapper;

    //保存操作
    @Test
    public void testSave(){
        mapper.save(new Person("aaa",20,"[email protected]",new Date()));
    }

    /**
     * 修改操作:
     * 修改与保存共用一个方法save
     * 当传入的实体类有主键就是修改,没有主键就是保存
     */
    @Test
    public void testUpdate(){
        Person person = new Person("aaa",20,"[email protected]",new Date());
        person.setId(8l);
        mapper.save(person);
    }
    //删除
    @Test
    public void testDelete(){
        Person person = new Person("aaa",20,"[email protected]",new Date());
        person.setId(8l);
        mapper.delete(person);
    }
    //根据ID查询
    @Test
    public void testFindById(){
        System.out.println(mapper.findById(7l).get());
    }
    //查询所有
    @Test
    public void testFindAll(){
        mapper.findAll().forEach(System.out::println);
    }
}

3.SpringDataJPA的底层代码分析

3.1常用接口分析

在自定义mapper接口中,并没有提供任何方法就可以使用其中的方法,那么这些方法究竟是怎么来的?,由于我们的接口继承了JpaRepositoryJpaSpecificationExecutor,所以我们可以使用这两个接口的所有方法。

在使用Spring Data JPA时,一般实现JpaRepositoryJpaSpecificationExecutor接口,这样就可以使用这些接口中定义的方法,但是这些方法都只是一些声明,没有具体的实现方式,那么在 Spring Data JPA中它又是怎么实现的呢?

3.2SpringDataJPA的实现过程

通过刚才的入门案例,打断点来分析SpringDataJPA的执行过程。

以findById()为例。

在这里插入图片描述

断点执行到方法上时,我们可以发现注入的是personmapper对象,本质上是通过jdk的动态代理生成的一个对象。

查看具体过程:

在这里插入图片描述

在这里插入图片描述

当程序执行的时候,会通过JdkDynamicAopProxyinvoke方法,对Dao对象生成动态代理对象。根据对Spring Data JPA介绍而知,要想进行findOne查询方法,最终还是会出现JPA规范的API完成操作,那么这些底层代码存在于何处呢?答案很简单,都隐藏在通过JdkDynamicAopProxy生成的动态代理对象当中,而这个动态代理对象就是SimpleJpaRepository,通过 SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?

继续查找em对象,我们发现em就是EntityManager对象,而他是JPA原生的实现方式,所以我们得到结论Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发。

由此可得:SpringDataJpa完整执行流程:

SpringDataJpa–>JPA–>hibernate–>数据库

4.SpringDataJPA的查询方式

4.1使用jpql的方式查询

使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询

@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可

    /**
     * 测试使用JPQL的方式进行查询
     */
    @Query("from Person ")
    List<Person> MyfindAll();
    @Query("from Person where id=?1")
    Person MyfindById(Long id);
    @Query(value = "update Person set name=?1 where id=?2")
    @Modifying
    @Transactional
    void MyUpdate(String name,Long id);

4.2使用SQL语句查询

    /**
     * nativeQuery : 使用本地sql的方式查询
     */
    @Query(value = "select * from t_person",nativeQuery = true)
    List<Person> MyfindAll();
    @Query(value = "select * from t_person where id=?1",nativeQuery = true)
    Person MyfindById(Long id);
    @Query(value = "update t_person set p_name=?1 where id=?2",nativeQuery = true)
    @Modifying
    @Transactional
    void MyUpdate(String name,Long id);

4.3方法命名规则查询

方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询

按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

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)

三,springdataJpa的动态查询与多表查询

1.动态查询

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

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

/**
 *	JpaSpecificationExecutor中定义的方法
 **/
 public interface JpaSpecificationExecutor<T> {
   	//根据条件查询一个对象
 	T findOne(Specification<T> spec);	
   	//根据条件查询集合
 	List<T> findAll(Specification<T> spec);
   	//根据条件分页查询
 	Page<T> findAll(Specification<T> spec, Pageable pageable);
   	//排序查询查询
 	List<T> findAll(Specification<T> spec, Sort sort);
   	//统计查询
 	long count(Specification<T> spec);
}

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

Specification接口中只定义了如下一个方法:

 //构造查询条件
    /**
    *	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
    *	query	:代表一个顶层查询对象,用来自定义查询
    *	cb		:用来构建查询,此对象里有很多条件方法
    **/
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

1.1 使用Specifications完成条件查询

    //动态条件查询
    @Test
    public void test7(){
        //使用匿名了内部类的方式,创建一个Specification的实现类,并实现toPredicate()
        Specification<Person> sp=new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //criteriaBuilder:构建查询,添加查询方式 like:模糊匹配
                return criteriaBuilder.like(root.get("name").as(String.class),"少妇白洁");
            }
        };
        mapper.findAll(sp).forEach(System.out::println);

    }

1.2基于Specification的分页查询

    //动态分页查询
    @Test
    public void test8(){
        //构建查询条件
        Specification<Person> sp=new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

                return criteriaBuilder.like(root.get("name").as(String.class),"少妇%");
            }
        };
        /**
         * 构建分页参数:
         *  Pageable:接口
         *  PageRequet实现了Pageable接口,调用of方法的形式构造。
         *      第一个参数:页码(从0开始)
         *      第二个参数:每页查询条数
         */
        Pageable request = PageRequest.of(0,1);
        /**
         * 分页查询:封装为SpringDataJpa内部的pageBean
         * 此重载findAlll方法为分页方法需要两个参数
         *      第一个参数、;查询条件
         *      第二个参数:分页参数
         */
        Page<Person> page = mapper.findAll(sp, request);
        page.forEach(System.out::println);

    }

1.3方法对应关系

方法名称 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

2.多表设计

2.1表之间关系的划分

数据库中多表之间存在着三种关系,如图所示。

在这里插入图片描述

从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。

2.2 在JPA框架中表关系的分析步骤

在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以学习重点是:掌握配置实体之间的关联关系。

第一步:首先确定两张表之间的关系。

​ 如果关系确定错了,后面做的所有操作就都不可能正确。

第二步:在数据库中实现两张表的关系

第三步:在实体类中描述出两个实体的关系

第四步:配置出实体类和数据库表的关系映射(重点)

3.jpa中的一对多

3.1情景模拟

上个案例我创建的是一张Person表,此时在创建一张Dog表,每个人都可以对应着多个宠物狗。

此时从人的角度就是一对多的关系。

3.2表关系建立

在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。

什么是外键?

指的是从表中有一列,取值参照主表的主键,这一列就是外键。

一对多数据库关系的建立,如下图所示

在这里插入图片描述

3.3 实体类关系建立以及映射配置

数据库建表语句

CREATE TABLE t_person(
id INT PRIMARY KEY AUTO_INCREMENT,
p_age INT ,
p_birth DATE,
p_email VARCHAR(20),
p_name VARCHAR(20)
);
CREATE TABLE t_dog(
d_id INT PRIMARY KEY AUTO_INCREMENT,
d_name VARCHAR(20),
p_id INT REFERENCES t_person(id) 
);

实体类关系映射

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

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id")
    private Long id;
    @Column(name = "p_name")
    private String name;
    @Column(name = "p_age")
    private Integer age;
    @Column(name = "p_email")
    private String email;
    @Column(name = "p_birth")
    private Date birth;
    @OneToMany(mappedBy="person")
    private List<Dog> dogs=new ArrayList<>();
}
@Entity
@Table(name = "t_dog")
public class Dog {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "d_id")
    private Long id;
    @Column(name = "d_name")
    private String name;
    @ManyToOne(targetEntity = Person.class)
    @JoinColumn(name = "p_id",referencedColumnName = "id")
    private Person person;
}

3.4映射的注解说明

@OneToMany:

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

属性:

​ targetEntityClass:指定多的多方的类的字节码

​ mappedBy:指定从表实体类中引用主表对象的名称。

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

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

​ orphanRemoval:是否使用孤儿删除

@ManyToOne

作用:建立多对一的关系

属性:

​ targetEntityClass:指定一的一方实体类字节码

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

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

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

@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。

属性:

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

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

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

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

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

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

​ columnDefinition:列的定义信息。

3.5一对多的操作

1)添加

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

    @Autowired
    private PersonMapper personMapper;
    @Autowired
    private DogMapper dogMapper;

    /**
     * 测试保存:
     *      同时保存一个狗主人和一只狗的信息
     */
    @Test
    @Transactional
    @Rollback(value = false)
    public void testSave(){
        Person person = new Person("张敏",26,"[email protected]",new Date());
        Dog dog = new Dog();
        dog.setName("金毛");
        person.getDogs().add(dog);
        dog.setPerson(person);
        personMapper.save(person);
        dogMapper.save(dog);
    }
}

2)删除

    /**
     * 删除操作
     */
    @Test
    public void testDelete(){
        //删除主表数据,可以直接删除,对应从表外键字段还是删除的主表ID
        personMapper.deleteById(4l);
        //此时在删除从表会报错
        dogMapper.deleteById(4l);
        //如果先删除从表再删除主表则没有问题
    }

3)级联操作

级联操作:指操作一个对象同时操作它的关联对象

使用方法:只需要在操作主体的注解上配置cascade

@OneToMany(mappedBy="person",cascade = CascadeType.ALL)

cascade:配置级联操作
CascadeType.MERGE 级联更新
CascadeType.PERSIST 级联保存:
CascadeType.REFRESH 级联刷新:
CascadeType.REMOVE 级联删除:
CascadeType.ALL 包含所有

4.jpa中的多对多

4.1.情景模拟

用户和角色:一个用户可以有多个角色,一个角色可以对应多个用户

4.2表关系建立

在这里插入图片描述

CREATE TABLE t_role(
r_id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
CREATE TABLE t_person_role(
id INT PRIMARY KEY AUTO_INCREMENT,
r_id INT REFERENCES t_role(r_id),
p_id INT REFERENCES t_person(id)
)

4.3实体关系建立以及映射配置

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

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id")
    private Long id;
    @Column(name = "p_name")
    private String name;
    @Column(name = "p_age")
    private Integer age;
    @Column(name = "p_email")
    private String email;
    @Column(name = "p_birth")
    private Date birth;
    @OneToMany(mappedBy="person",cascade = CascadeType.ALL)
    private List<Dog> dogs=new ArrayList<>();
    //多对多关系映射
    @ManyToMany(mappedBy ="persons")
    private List<Role> roles=new ArrayList<>();
    }
@Entity
@Table(name = "t_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "r_id")
    private Integer id;
    @Column(name = "name")
    private String name;
    @ManyToMany
    @JoinTable(name="t_person_role",//中间表的名称
            joinColumns={@JoinColumn(name="r_id",referencedColumnName="r_id")},
            inverseJoinColumns={@JoinColumn(name="p_id",referencedColumnName="id")}
    )
    private List<Person> persons=new ArrayList<>();
    }

4.4映射的注解说明

@ManyToMany

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

​ 属性:

​ cascade:配置级联操作。

​ fetch:配置是否采用延迟加载。

​ targetEntity:配置目标的实体类。映射多对多的时候不用写。

@JoinTable

作用:针对中间表的配置

属性:

​ nam:配置中间表的名称

​ joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段

​ inverseJoinColumn:中间表的外键字段关联对方表的主键字段

@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。

属性:

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

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

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

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

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

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

​ columnDefinition:列的定义信息。

4.5多对多的操作

/**
 * @author yinhuidong
 * @createTime 2020-06-04-21:19
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ManyToManyTest {

    @Autowired
    private PersonMapper personMapper;
    @Autowired
    private RoleMapper roleMapper;

    /**
     * 保存
     */
    @Test
    @Transactional
    @Rollback(false)
    public void test1(){
        Person person = new Person();
        person.setName("尹会东");
        Person person2 = new Person();
        person2.setName("张贝贝");
        Role r1 = new Role();
        r1.setName("学生");
        Role r2 = new Role();
        r2.setName("丈夫");
        person.getRoles().add(r1);
        person.getRoles().add(r2);
        person2.getRoles().add(r1);
        person2.getRoles().add(r2);
        r1.getPersons().add(person);
        r2.getPersons().add(person);
        r1.getPersons().add(person2);
        r2.getPersons().add(person2);
        personMapper.save(person);
        personMapper.save(person2);
        roleMapper.save(r1);
        roleMapper.save(r2);

    }
    @Test
    @Transactional
    @Rollback(false)
    public void test2(){
        personMapper.deleteById(10l);
    }

}

5.SpringDataJPA的多表查询

对象导航图查询

对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。

查询一个Person获取它对应的所有Role

    @Transactional
    @Test
    public void test3(){
        Person person = personMapper.findById(11l).get();
        System.out.println("person = " + person);
        person.getRoles().forEach(System.out::println);
    }

查询一个角色,获取角色对应的所有person

    @Transactional
    @Test
    public void test3(){
        Role role = roleMapper.findById(5l).get();
        System.out.println("role = " + role);
        role.getPersons().forEach(System.out::println);
    }

对象导航查询的问题分析

问题1:我们查询客户时,要不要把联系人查询出来?

分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。

解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。

配置方式:

//LAZY
    @ManyToMany(mappedBy ="persons",fetch = FetchType.EAGER)

FetchType.EAGER 立即加载

FetchType.LAZY 懒加载

    r1.getPersons().add(person);
    r2.getPersons().add(person);
    r1.getPersons().add(person2);
    r2.getPersons().add(person2);
    personMapper.save(person);
    personMapper.save(person2);
    roleMapper.save(r1);
    roleMapper.save(r2);

}
@Test
@Transactional
@Rollback(false)
public void test2(){
    personMapper.deleteById(10l);
}

}


## 5.SpringDataJPA的多表查询

### 5.1对象导航图查询

对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。

**查询一个Person获取它对应的所有Role**

```java
    @Transactional
    @Test
    public void test3(){
        Person person = personMapper.findById(11l).get();
        System.out.println("person = " + person);
        person.getRoles().forEach(System.out::println);
    }

查询一个角色,获取角色对应的所有person

    @Transactional
    @Test
    public void test3(){
        Role role = roleMapper.findById(5l).get();
        System.out.println("role = " + role);
        role.getPersons().forEach(System.out::println);
    }

对象导航查询的问题分析

问题1:我们查询客户时,要不要把联系人查询出来?

分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。

解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。

配置方式:

//LAZY
    @ManyToMany(mappedBy ="persons",fetch = FetchType.EAGER)

FetchType.EAGER 立即加载

FetchType.LAZY 懒加载

猜你喜欢

转载自blog.csdn.net/weixin_45596022/article/details/108504223