SpringData学习笔记01_SpringDataJPA

SpringData学习笔记01

根据 黑马程序员 视频教程《java进阶教程数据层全栈方案Spring Data高级应用》进行整理记录,仅用于个人学习/交流使用。

视频地址:https://www.bilibili.com/video/BV1RE41167Pk

第一章 SpringData概述

持久层开发的问题

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

image-20210817112502585

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

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

SpringData简介

什么是SpringData

SpringData是一个用来简化dao层开发的框架。它在保证了各个底层存储特性的同时,提供了一套统一的数据访问API。它可以很好的支持常用的关系型数据库非关系型数据库。 使用SpringData作为dao层开发技术,将大大简化代码量,而且其API比各个技术的原生API更加简 单易用。

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实现访问操作

第二章 JPA回顾

JPA基础

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

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

image-20210817112640474

JPA实战

目标

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

数据库

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

创建工程,导入坐标

    <dependencies>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!--Hibernate-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.0.7.Final</version>
        </dependency>

        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

    </dependencies>

创建实体类

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

在实体类中配置映射关系

import javax.persistence.*;
import java.util.Date;

@Entity//告诉jpa这是一个实体类,需要把它跟数据库中的表做映射
//使用注解建立实体类和数据表之间的对应关系
@Table(name = "article")//@Table建立了实体类和数据表的关系  name指向表名
public class Article {
    @Id//标识这是主键字段
    //指定主键生成策略,GenerationType.IDENTITY就是对应到mysql中的数据自增策略
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer aid;
    //使用@Column映射类的属性和数据表的字段关系  name指定表中的字段名
    //当类的属性名和数据表的字段名一致时,此注解可省略
    @Column(name = "author")
    private String author;
    private Date createTime;
    private String title;
    //省略set和get方法。。。
	//省略toString方法。。。
}    

加入 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">

    <!--持久化单元
       name 持久化单元的名称 唯一
       transaction-type  事务类型
            RESOURCE_LOCAL  本地事务
            JTA   分布式事务
    -->
    <persistence-unit name="jpa01" transaction-type="RESOURCE_LOCAL">

        <!--配置 JPA 规范的服务提供商,当项目中只有一个JPA的实现时,此选项可省略-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!--指定实体类,此选项可省略-->
        <class>com.itheima.domain.Article</class>

        <properties>
            <!--跟数据库相关的信息 驱动 url 用户名 密码-->
            <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="88888888"/>

            <!--jpa的核心配置中兼容hibernate的配置-->
            <!--是否显示SQL-->
            <property name="hibernate.show_sql" value="true"/>
            <!--是否格式化显示的SQL-->
            <property name="hibernate.format_sql" value="true"/>
            <!--
                自动建表
                    update  如果数据库存在数据表,就使用;不存在,就创建
                    create  不管数据库有没有数据表,每次SQL请求都会重新建表
            -->
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>


</persistence>

测试

import com.itheima.domain.Article;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.Date;

public class JpaTest {

    /**
     * 添加的方法
     */
    @Test
    public void testSave() {
        //1 创建持久化管理器工厂
        String persistenceUnitName = "jpa01";
        EntityManagerFactory factory = Persistence.createEntityManagerFactory(persistenceUnitName);

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

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

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

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

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

    /**
     * 根据ID查询
     */
    @Test
    public void testFindByAid() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa01");
        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();
    }

    /**
     * 更新
     * 注:需要先查询,后修改
     */
    @Test
    public void testUpdate() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa01");
        EntityManager entityManager = factory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();

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

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

    }

    /**
     * 删除
     * 注:需要先查询,后删除
     */
    @Test
    public void testDelete() {
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa01");
        EntityManager entityManager = factory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();

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

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

JPA的重要API介绍

EntityManagerFactory

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

EntityManager

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

第三章 SpringDataJPA基础

SpringData JPA简介

SpringData JPA是Spring Data家族的一个成员,是Spring Data对JPA封装之后的产物,目的在于简化基于JPA的数据访问技术。

使用SpringData JPA技术之后,开发者只需要声明Dao层的接口,不必再写实现类或其它代码,剩下的一切交给SpringData JPA来搞定。

SpringData JPA快速入门

目标

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

准备数据环境

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

创建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>
        <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>


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

    </dependencies>

创建实体类

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

在实体类中配置映射关系

import javax.persistence.*;
import java.util.Date;

@Entity//告诉jpa这是一个实体类,需要把它跟数据库中的表做映射
//使用注解建立实体类和数据表之间的对应关系
@Table(name = "article")//@Table建立了实体类和数据表的关系  name指向表名
public class Article {
    @Id//标识这是主键字段
    //指定主键生成策略,GenerationType.IDENTITY就是对应到mysql中的数据自增策略
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer aid;
    //使用@Column映射类的属性和数据表的字段关系  name指定表中的字段名
    //当类的属性名和数据表的字段名一致时,此注解可省略
    @Column(name = "author")
    private String author;
    private Date createTime;
    private String title;
    //省略set和get方法。。。
	//省略toString方法。。。
}    

编写dao接口

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

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

  1. 创建一个 Dao 层接口,并实现 JpaRepositoryJpaSpecificationExecutor
  2. 提供相应的泛型

添加Spring整合Jpa的配置文件

注意配置:数据的驱动,url,账号,密码;实体类的扫描路径,Dao层的扫描路径

<?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="com.atheima"/>

    <!--配置一个数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///test"/>
        <property name="username" value="root"/>
        <property name="password" value="88888888"/>
    </bean>

    <!--配置EntityManagerFactory 可以产生entityManger-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!--配置一个数据源-->
        <property name="dataSource" ref="dataSource"/>

        <!--指定实体类-->
        <property name="packagesToScan" value="com.atheima.domain"/>

        <!--配置服务的提供商-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>

        <!--SpringData Jpa 兼容Hibernate使用-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--配置数据库名称-->
                <property name="database" value="MYSQL"/>
                <!--是否自动建表  true 自动建表  false 不会自动建表-->
                <property name="generateDdl" value="true"/>
                <!--是否显示SQL-->
                <property name="showSql" value="true"/>
            </bean>
        </property>
    </bean>

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


    <!--做一个jpa:repository的配置-->
    <!--base-package 配置dao包的包名 它会为这个包先所有的接口动态产生代理对象-->
    <jpa:repositories base-package="com.atheima.dao"
                      entity-manager-factory-ref="entityManagerFactory"
                      transaction-manager-ref="transactionManager"
    />

</beans>

测试

测试环境配置

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-jpa.xml")
import com.atheima.dao.ArticleDao;
import com.atheima.domain.Article;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Date;
import java.util.List;
import java.util.Optional;

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

    @Autowired
    private ArticleDao articleDao;

    //保存
    @Test
    public void testSave() {
        Article article = new Article();
        article.setTitle("黑马程序员1");
        article.setAuthor("黑马2");
        article.setCreateTime(new Date());

        articleDao.save(article);
    }


    //查询主键
    @Test
    public void testFindByAid() {
        Optional<Article> optional = articleDao.findById(1);
        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 testUpdate() {
        Article article = new Article();
        article.setAuthor("黑马3");
        article.setAid(2);

        //Spingdata Jpa的保存和修改使用的都是save方法
        //关键来看传入的实体是否有主键
        //---如果有主键,代表要修改
        //---如果没有主键,代表要保存
        articleDao.save(article);
    }


    //修改
    @Test
    public void testUpdate2(){
        //如果使用上面的方法更新时,会导致没有赋值的属性变为null
        //因此,建议先查询,后修改
        Optional<Article> optional = articleDao.findById(1);
        Article article=optional.get();
        article.setAuthor("黑马33");
        articleDao.save(article);
    }

    //删除
    @Test
    public void testDelete() {
        articleDao.deleteById(2);
    }
}

SpringData Jpa运行原理分析

SpringData中的几个重要接口

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

自定义的接口继承了两个接口,方法肯定来自里面,追踪关系得到下面的继承关系

Repository 标记接口:继承了此接口后会被Spring识别,进而可以在接口中声明一些满足规范的方法
|
|
CrudRepository 实现了基本增删改查方法
|
|
PagingAndSortingRepository 实现了分页和排序的方法
|
|
JpaRepository 重写了几个查找和删除的方法
|
|
ArticleDao

通过上面的继承关系,我们可以看到我们自定义的接口ArticleDao继承了一系列的Repository接口,而每一个接口都会给我们提供一部分的功能,这样继承下来,我们的ArticleDao不用任何的方法声明就拥有了很多的功能了。

SpringData Jpa底层运行原理

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

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

  1. 在运行时,Spring会使用JdkDynamicAopProxy为dao接口生成一个代理对象
image-20210817145938283 image-20210817151955044

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

image-20210817152106983
  1. 通过对SimpleJpaRepository中代码的分析,我们看到最终执行保存的是EntityManager对象
image-20210817152157956

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

SpringData Jpa 与 Jpa 及 Hibernate的关系

image-20210817152253126

第四章 SpringData JPA的多种查询方式

父接口方法查询

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

image-20210818094730078

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

    @Autowired
    private ArticleDao articleDao;

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

        //根据多个主键查询
        List<Integer> list = new ArrayList<>();
        list.add(21);
        list.add(23);
        list.add(25);
        List<Article> articles = articleDao.findAllById(list);
        for (Article article : articles) {
            System.out.println(article);
        }
    }


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

    //查询所有--排序
    @Test
    public void testFindAllWithSort() {
        //按照aid倒序排列
        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() {
        //处理分页条件
        //page   当前是第几页(从0开始)    size  每页大小
        Pageable pageable = PageRequest.of(0, 2);
        Page<Article> page = articleDao.findAll(pageable);

        //总记录数  总页数  每页多少
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("每页多少:" + page.getSize());
        //当前页的元素
        List<Article> content = page.getContent();
        for (Article article : content) {
            System.out.println(article);
        }
    }


    //查询所有--分页+排序
    @Test
    public void testFindAllWithPageAndPage() {
        //按照aid倒序排列
        Sort sort = Sort.by(Sort.Order.desc("aid"));

        //处理分页条件
        //page   当前是第几页(从0开始)    size  每页大小
        Pageable pageable = PageRequest.of(0, 2, sort);
        Page<Article> page = articleDao.findAll(pageable);

        //总记录数  总页数  每页多少
        System.out.println("总记录数:" + page.getTotalElements());
        System.out.println("总页数:" + page.getTotalPages());
        System.out.println("每页多少:" + page.getSize());
        //当前页的元素
        List<Article> content = page.getContent();
        for (Article article : content) {
            System.out.println(article);
        }
    }

}

方法命名规则查询

顾名思义,方法命名规则查询就是根据方法的名字就能创建查询。只需要按照SpringData JPA提供的方法命名规则 定义方法的名称,就可以完成查询工作。

SpringData JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询.

按照SpringData JPA定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接

要注意的是:条件属性首字母需大写。

框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

//自定义接口需要继承JpaRepository<实体类的类型,实体类中的主键的类型>, JpaSpecificationExecutor<实体类的类型>
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范围查询 > < between in
    List<Article> findByAidIsLessThan(Integer aid);

    List<Article> findByAidBetween(Integer startAid, Integer endAid);

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

    //根据创建时间之后查询
    List<Article> findByCreateTimeAfter(Date createTime);

    
}

image-20210818095602453

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

    @Autowired
    private ArticleDao articleDao;

    @Test
    public void testFindByTitle() {
        List<Article> articles = articleDao.findByTitle("黑马程序员1");
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByTitleLike() {
        List<Article> articles = articleDao.findByTitleLike("%黑马程序员%");
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByTitleAndAuthor() {
        List<Article> articles = articleDao.findByTitleAndAuthor("黑马程序员1", "黑马");
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByAidIsLessThan() {
        List<Article> articles = articleDao.findByAidIsLessThan(25);
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByAidBetween() {
        List<Article> articles = articleDao.findByAidBetween(25, 30);
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByAidIn() {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(29);
        list.add(30);
        List<Article> articles = articleDao.findByAidIn(list);
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByCreateTimeAfter() {
        List<Article> articles = articleDao.findByCreateTimeAfter(new Date());
        for (Article article : articles) {
            System.out.println(article);
        }
    }


}

JPQL查询

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

JPQL,全称是Java Persistence Query Language。

JPQL语句是JPA中定义的一种查询语言,此种语言的用意是让开发者忽略数据库表和表中的字段,而关注实体类及实体类中的属性。 它的写法十分类似于SQL语句的写法,但是要把查询的表名换成实体类名称,把表中的字段名换成实体类 的属性名称。


//自定义接口需要继承JpaRepository<实体类的类型,实体类中的主键的类型>, JpaSpecificationExecutor<实体类的类型>
public interface ArticleDao extends JpaRepository<Article, Integer>, JpaSpecificationExecutor<Article> {	
	//JPQL:类似于SQL语句,但是要使用实体类名代替表名,使用属性名代替字段名[面向对象查询]
    //展示位置参数绑定[按照title和author查询]
    //占位符从1开始
    @Query("from Article a where a.title = ?1 and a.author =?2")
    List<Article> findByCondition1(String title, String author);

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

    //展示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 a.aid desc ")
    List<Article> findByCondition4(@Param("title") String title);

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

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

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


}    
package com.itheima.test;

import com.itheima.dao.ArticleDao;
import com.itheima.domain.Article;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

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

    @Autowired
    private ArticleDao articleDao;


    @Test
    public void testFindByCondition1() {
        List<Article> articles = articleDao.findByCondition1("黑马程序员1", "黑马");
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByCondition2() {
        List<Article> articles = articleDao.findByCondition2("黑马程序员1", "黑马");
        for (Article article : articles) {
            System.out.println(article);
        }
    }


    @Test
    public void testFindByCondition4() {
        List<Article> articles = articleDao.findByCondition4("黑马程序员");
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByCondition5() {
        Pageable pageable = PageRequest.of(0, 3);
        List<Article> articles = articleDao.findByCondition5(pageable, "黑马程序员");
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByCondition6() {
        List<Integer> list = new ArrayList<>();
        list.add(29);
        list.add(30);

        List<Article> articles = articleDao.findByCondition6(list);
        for (Article article : articles) {
            System.out.println(article);
        }
    }

    @Test
    public void testFindByCondition7() {
        Article articleParam = new Article();
        articleParam.setTitle("黑马程序员1");
        articleParam.setAuthor("黑马");
        List<Article> articles = articleDao.findByCondition7(articleParam);
        for (Article article : articles) {
            System.out.println(article);
        }
    }
}

本地SQL查询

//nativeQuery=true表示使用本地SQL查询 

//基本不会使用,除非是出现非常复杂的业务情况导致SQL非常复杂,JPQL搞不定的时候 
//因为MYSQL数据库 与 Oracle数据库 语法格式有所差异,这样写会 锁死SQL语句,不方便切换数据库

    //本地SQL查询
    @Query(value = "select * from article a where a.title = ?1 and a.author =?2", nativeQuery = true)
    List<Article> findByCondition8(String title, String author);
    @Test
    public void testFindByCondition8() {
        List<Article> articles = articleDao.findByCondition8("黑马程序员1", "黑马");
        for (Article article : articles) {
            System.out.println(article);
        }
    }

Specifications动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句

在 Spring Data JPA 中可以通过 JpaSpecificationExecutor 接口查询。相比 JPQL,其优势是类型安全,更 加的面向对象,缺点是书写比较麻烦。

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

    @Autowired
    private ArticleDao articleDao;

    //按照标题和作者进行查询,以不为空的属性作为查询条件
    @Test
    public void testFindAll() {

        //就模拟从从外边传入的变量
        String title = "黑马程序员3";
        String author = "黑马3";

        List<Article> articles = articleDao.findAll(new Specification<Article>() {

            /**
             * @param root  代表实体对象,我们可以通过它获取属性值
             * @param cq    用于生成SQL语句
             * @param cb    用于拼接查询条件
             * @return
             */
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

                List<Predicate> list = new ArrayList<>();
                if (!StringUtils.isEmpty(title)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("title").as(String.class), title);
                    list.add(predicate);
                }
                if (!StringUtils.isEmpty(author)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("author").as(String.class), author);
                    list.add(predicate);
                }

                return cb.and(list.toArray(new Predicate[]{}));
            }
        });

        for (Article article : articles) {
            System.out.println(article);
        }
    }


    @Test
    public void testFindAllWithPage() {
        //就模拟从从外边传入的变量
        String title = "";
        String author = "";

        //分页
        Pageable pageable = PageRequest.of(0, 3);

        Page<Article> page = articleDao.findAll(new Specification<Article>() {

            /**
             * @param root  代表实体对象,我们可以通过它获取属性值
             * @param cq    用于生成SQL语句
             * @param cb    用于拼接查询条件
             * @return
             */
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

                List<Predicate> list = new ArrayList<>();
                if (!StringUtils.isEmpty(title)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("title").as(String.class), title);
                    list.add(predicate);
                }
                if (!StringUtils.isEmpty(author)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("author").as(String.class), author);
                    list.add(predicate);
                }

                return cb.and(list.toArray(new Predicate[]{}));
            }
        }, pageable);

        for (Article article : page.getContent()) {
            System.out.println(article);
        }
    }


    @Test
    public void testFindAllWithPageAndSort() {

        //就模拟从从外边传入的变量
        String title = "";
        String author = "";

        //分页
        Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Order.desc("aid")));

        Page<Article> page = articleDao.findAll(new Specification<Article>() {

            /**
             * @param root  代表实体对象,我们可以通过它获取属性值
             * @param cq    用于生成SQL语句
             * @param cb    用于拼接查询条件
             * @return
             */
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

                List<Predicate> list = new ArrayList<>();
                if (!StringUtils.isEmpty(title)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("title").as(String.class), title);
                    list.add(predicate);
                }
                if (!StringUtils.isEmpty(author)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("author").as(String.class), author);
                    list.add(predicate);
                }

                return cb.and(list.toArray(new Predicate[]{}));
            }
        }, pageable);

        for (Article article : page.getContent()) {
            System.out.println(article);
        }
    }
}

第五章 SpringData JPA实现多表操作

多表关系分析

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

image-20210819091548106
从图可以看出,系统设计的三种实体关系分别为: 多对多、一对多和一对一关系。
注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。
而在这种实现了ORM思想的框架中(如 JPA),可以让我们通过操作实体类就实现对数据库表的操作。
所以今天我们的学习重点是:掌握配置实体之间的关联关系
第一步:首先确定两张表之间的关系
第二步:在实体类中描述出两个实体的关系
第三步:配置出实体类和数据库表的关系映射(重点)

掌握配置实体之间的关联关系

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

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

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

案例表间关系

image-20210819091804755

一对一关系

数据环境(article和article_data的一对一关系)

image-20210819091800036

创建实体类,并在类中配置表间关系

创建文章类

package com.itheima.test;

@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;
    //建立从文章内容到文章的一对一关系
    //设置级联操作,当操作article的时候,同时级联操作article_data的信息
    @OneToOne(mappedBy = "article", cascade = CascadeType.ALL)
    private ArticleData articleData;
	//省略set和get方法。。。	
	//省略toString方法。。。
}

创建文章详情类

@Entity
@Table(name = "article_data")
public class ArticleData {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String content;
    //建立从文章到文章内容的一对一关系
	//使用@JoinColumn声明维护外键关系,当前表中的外键articleId指向对方表的主键aid
    @OneToOne
    @JoinColumn(name = "articleId", referencedColumnName = "aid", unique = true)
    private Article article;
	//省略set和get方法。。。
	//省略toString方法。。。
}

添加ArticleDao接口

public interface ArticleDataDao extends JpaRepository<ArticleData, Integer>,
	JpaSpecificationExecutor<ArticleData> {
}

测试

package com.itheima.test;

@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("黑马");
        article.setCreateTime(new Date());
		//添加文章内容信息
        ArticleData articleData = new ArticleData();
        articleData.setContent("测试");
		//建立两者关系
        article.setArticleData(articleData);
        articleData.setArticle(article);
		//保存
        articleDao.save(article);
    }
}

一对多关系

数据环境(article和comment的一对多关系)

image-20210819092452699

创建实体类,并在类中配置表间关系

修改文章类,添加文章跟评论的映射

@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;
    //建立文章对评论的一对多关系
	//在一的一方声明放弃维护关系
    @OneToMany(mappedBy = "article")
    private Set<Comment> comments = new HashSet<>(0);
    //建立从文章内容到文章的一对一关系
	//设置级联操作,当操作article的时候,同时级联操作article_data的信息
    @OneToOne(mappedBy = "article", cascade = CascadeType.ALL)
    private ArticleData articleData;
    //省略set和get
	//省略toString方法。。。
}

创建文章评论类

@Entity
@Table(name = "comment")
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer cid;
    private String comment;
    //建立评论到文章的多对一关系
//在多的一方维护关系
    @ManyToOne
    @JoinColumn(name = "aid",referencedColumnName = "aid")
    private Article article;
//省略set和get方法。。。
//省略toString方法。。。
}

添加CommentDao接口

public interface CommentDao extends JpaRepository<Comment, Integer>,
	JpaSpecificationExecutor<Comment> {
}

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ArticleDaoTest {
    @Autowired
    private ArticleDao articleDao;
    @Autowired
    private CommentDao commentDao;

    //新增
    @Test
    public void testSave() {
//添加文章信息
        Article article = new Article();
        article.setTitle("测试文章");
        article.setAuthor("黑马");
        article.setCreateTime(new Date());
//添加评论信息
        Comment comment1 = new Comment();
        comment1.setComment("好文章");
        Comment comment2 = new Comment();
        comment2.setComment("不错不错");
//建立两者关系
        comment1.setArticle(article);
        comment2.setArticle(article);
        article.getComments().add(comment1);
        article.getComments().add(comment2);
//保存
        articleDao.save(article);
        commentDao.save(comment1);
        commentDao.save(comment2);
    }
}

多对多关系

数据环境(article跟type之间的多对多关系)

image-20210819092851411

创建实体类,并在类中配置表间关系

修改文章类,添加文章跟评论用户的多对多关系

@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;
    //建立从文章到类型的多对多关系
    @ManyToMany(mappedBy = "articles")
    private Set<Type> types = new HashSet<>(0);
    //建立文章对评论的一对多关系
	//在一的一方声明放弃维护关系
    @OneToMany(mappedBy = "article")
    private Set<Comment> comments = new HashSet<>(0);
	//建立从文章内容到文章的一对一关系
	//设置级联操作,当操作article的时候,同时级联操作article_data的信息
    @OneToOne(mappedBy = "article", cascade = CascadeType.ALL)
    private ArticleData articleData;
}

添加用户类,并在类中配置表间关系

@Entity
@Table(name = "type")
public class Type {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer tid;
    private String name;
    //建立从类型到文章的多对多关系
    @ManyToMany
    @JoinTable(
//中间表名称
            name = "article_type",
//中间表的外键字段关联当前实体类所对应表的主键字段
            joinColumns = {@JoinColumn(name = "tid", referencedColumnName = "tid")},
//中间表的外键字段关联对方类所对应表的主键字段
            inverseJoinColumns = {@JoinColumn(name = "aid", referencedColumnName =
                    "aid")}
    )
    private Set<Article> articles = new HashSet<>(0);
}

添加TypeDao接口

public interface TypeDao extends JpaRepository<Type, Integer>,
	JpaSpecificationExecutor<Type> {
}

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ArticleDaoTest {
    @Autowired
    private ArticleDao articleDao;
    @Autowired
    private CommentDao commentDao;
    //新增
    @Test
    public void testSave() {
//添加文章信息
        Article article1 = new Article();
        article1.setTitle("测试文章1");
        Article article2 = new Article();
        article2.setTitle("测试文章2");
//添加类型
        Type type1 = new Type();
        type1.setName("军事");
        Type type2 = new Type();
        type2.setName("民生");
//建立两者关系
        article1.getTypes().add(type1);
        article1.getTypes().add(type2);
        article2.getTypes().add(type1);
        article2.getTypes().add(type2);
        type1.getArticles().add(article1);
        type1.getArticles().add(article2);
        type2.getArticles().add(article1);
        type2.getArticles().add(article2);
//保存
        articleDao.save(article1);
        articleDao.save(article2);
        typeDao.save(type1);
        typeDao.save(type2);
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值