SpringData JPA

目录

持久层开发的问题

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHELOZcJ-1610978924397)(assets/1562849771292.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9sUDfB5U-1610978924408)(assets/1562851211712.png)]

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 接口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ghxFrIzR-1610978924411)(assets/1560915655950.png)]

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

创建java工程,导入坐标

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

创建实体类

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

在实体类中配置映射关系

@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;
}

加入 JPA 的核心配置文件

在maven工程的resources路径下创建一个名为META-INF的文件夹,在文件夹下创建一个名为persistence.xml的配置文件。注意: META-INF文件夹名称不能修改,persistence.xml文件名称不能改。
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
             http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">

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

测试

实现保存操作

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

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

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

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

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

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

实现查询操作

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

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

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

实现修改操作

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

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

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

实现删除操作

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

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

JPA的重要API介绍

EntityManagerFactory

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

EntityManager

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

第三章 SpringData JPA基础

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>

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

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

创建实体类

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

在实体类中配置映射关系

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

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

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

编写dao接口

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

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

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

添加Spring整合Jpa的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

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

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

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

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

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

测试

@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());
        articleDao.save(article);
    }

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

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

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

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

SpringData Jpa运行原理分析

SpringData中的几个重要接口

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

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

SpringData Jpa底层运行原理

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

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ABSnPAd6-1610978924414)(assets/1560934038055.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h5AZPq3k-1610978924415)(assets/1560934602104.png)]

  3. 通过对SimpleJpaRepository中代码的分析,我们看到最终执行保存的是EntityManager对象
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bz8o1cHZ-1610978924416)(assets/1560934944565.png)]

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

SpringData Jpa 与 Jpa 及 Hibernate的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gn3o5saS-1610978924417)(assets/1560935373482.png)]

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

##父接口方法查询

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Li2x0Qla-1610978924418)(assets/1560958754218.png)]

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

    @Autowired
    private ArticleDao articleDao;

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

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

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

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

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

}

方法命名规则查询

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

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

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

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

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

    List<Article> findByAidLessThan(Integer endAid);

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

    //根据创建时间之后查询
    List<Article> findByCreateTimeAfter(Date createTime);
}
关键字 例子 对应的JPQL语句
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) = U

JPQL查询

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


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

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

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

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

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

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

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

本地SQL查询

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

Specifications动态查询

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

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

    @Autowired
    private ArticleDao articleDao;

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

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

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

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

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

第五章 SpringData JPA实现多表操作

多表关系分析

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-laykwZur-1610978924419)(assets/1560944638009.png)]

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

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

案例表间关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8eI5LmHC-1610978924419)(assets/1560957248242.png)]

一对一关系

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YPSobFcQ-1610978924420)(assets/1560951476914.png)]

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

创建文章类

@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> {
}

测试

@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的一对多关系)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tHSujo2a-1610978924421)(assets/1560951710639.png)]

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

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

@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之间的多对多关系)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M1aNz4yn-1610978924421)(assets/1560957287022.png)]

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

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

@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);
    }
}
上一篇:springData-JPA


下一篇:【SpringData JPA】springboot整合jpa