欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Data JPA学习

程序员文章站 2022-06-05 17:43:02
一、Spring Data JPA 1、简介 (1)官网地址: https://spring.io/projects/spring-data-jpa参考文档: https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/ht ......

一、spring data jpa

1、简介

(1)官网地址:
  https://spring.io/projects/spring-data-jpa
参考文档:
  https://docs.spring.io/spring-data/jpa/docs/2.2.3.release/reference/html/#preface

(2)基本介绍:
  spring data jpa 是 spring 基于 orm 框架、jpa 规范封装的一套 jpa 框架。使开发者通过极简的代码实现对数据库的访问和操作。
注:
  orm 框架:指的是 object relational mapping,即对象关系映射。采用元数据来描述对象和关系映射的细节。
  元数据:一般采用 xml 文件的形式。常见 orm 框架如:mybatis、hibernate。
  jpa:指的是 java persistence api,即 java 持久层 api。通过 xml 或注解的映射关系将运行期的实体对象持久化到数据库中。

2、sping boot 项目中使用

(1)在 pom.xml 文件中引入依赖

【pom.xml】

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-data-jpa</artifactid>
</dependency>

 

(2)在 application.properties 中配置

【application.properties】

# jpa 配置
# 配置数据库为 mysql
spring.jpa.database=mysql
# 在控制台打印 sql 语句
spring.jpa.show-sql=true
# 每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新
spring.jpa.hibernate.ddl-auto=update
# 每次运行该程序,没有表格会新建表格,表内有数据会清空
#spring.jpa.hibernate.ddl-auto=create

 

二、基本注解

1、@entity

@entity 写在类上,用于指明一个类与数据库表相对应。
    属性:
        name,可选,用于自定义映射的表名。若没有,则默认以类名为表名。

【举例1:默认类名为表名】
import javax.persistence.entity;

@entity
public class blog {
}

【举例2:自定义表名】
import javax.persistence.entity;

@entity(name="t_blog")
public class blog {
}

 

2、@table

@table 写在类上,一般与 @entity 连用,用于指定数据表的相关信息。
    属性:
        name, 对应数据表名。
        catalog, 可选,对应关系数据库中的catalog。
        schema,可选,对应关系数据库中的schema。

【举例:】
import javax.persistence.entity;
import javax.persistence.table;

@entity(name = "blog")
@table(name = "t_blog")
public class blog {
}

注:若 @entity 与 @table 同时定义了 name 属性,那以 @table 为主。

 

3、@id、@generatedvalue

@id 写在类中的变量上,用于指定当前变量为主键 id。一般与 @generatedvalue 连用。

@generatedvalue 与 @id 连用,用于设置主键生成策略(自增主键,依赖数据库)。
注:
    @generatedvalue(strategy = generationtype.auto) 主键增长方式由数据库自动选择,当数据
库选择auto方式时就会自动生成hibernate_sequence表。
    
    @generatedvalue(strategy = generationtype.identity) 要求数据库选择自增方式,oracle不
支持此种方式,mysql支持。
    
    @generatedvalue(strategy = generationtype.sequence) 采用数据库提供的sequence机制生
成主键,mysql不支持。


【举例:】
package com.lyh.blog.bean;

import lombok.data;

import javax.persistence.*;

@entity
@table(name = "t_blog")
@data
public class blog {
    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;
}

Spring Data JPA学习

 

 

 

4、@column

@column 写在类的变量上,用于指定当前变量映射到数据表中的列的属性(列名,是否唯一,是否允许为空,是否允许更新等)。
属性:
    name: 列名。 
    unique: 是否唯一 
    nullable: 是否允许为空 
    insertable: 是否允许插入 
    updatable: 是否允许更新 
    length: 定义长度
    
【举例:】
import lombok.data;

import javax.persistence.*;

@entity
@table(name = "t_blog")
@data
public class blog {
    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 36, unique = false, nullable = false, insertable = true, updatable = true)
    private string name;
}

Spring Data JPA学习

 

 

 

5、@temporal

@temporal 用于将 java.util 下的时间日期类型转换 并存于数据库中(日期、时间、时间戳)。
属性:
    temporaltype.date       java.sql.date日期型,精确到年月日,例如“2019-12-17”
    temporaltype.time       java.sql.time时间型,精确到时分秒,例如“2019-12-17 00:00:00”
    temporaltype.timestamp  java.sql.timestamp时间戳,精确到纳秒,例如“2019-12-17 00:00:00.000000001”

【举例:】
package com.lyh.blog.bean;

import lombok.data;

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

@entity
@table(name = "t_blog")
@data
public class blog {
    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 36, unique = false, nullable = false, insertable = true, updatable = true)
    private string name;

    @temporal(temporaltype.timestamp)
    private date createtime;

    @temporal(temporaltype.date)
    private date updatetime;
}

Spring Data JPA学习

 

 

 

6、级联(cascade)

对于 @onetoone、@manytomany、@onetomany等映射关系,涉及到级联的操作。
    cascadetype[] cascade() default {};
    定义级联用于 给当前设置的实体 操作 另一个关联的实体的权限。
    
【级联的类型:】
package javax.persistence;
public enum cascadetype {
    all,
    persist,
    merge,
    remove,
    refresh,
    detach;

    private cascadetype() {
    }
}

cascadetype.all         拥有所有级联操作的权限。
cascadetype.persist     当前实体类进行保存操作时,同时保存其关联的实体。
cascadetype.merge       当前实体数据合并时,会影响其关联的实体。
cascadetype.remove      删除当前实体,与其相关联的实体也会被删除。
cascadetype.refresh     刷新当前实体,与其相关联的实体也会被刷新。
cascadetype.detach      去除外键关联,当删一个实体时,存在外键无法删除,使用此级联可以去除外键。

 

7、mappedby

 只有 @onetoone, @onetomany, @manytomany上才有 mappedby 属性,@manytoone不存在该属性。
 
 该属性的作用:
     设置关联关系。单向关联关系不需要设置,双向关系必须设置,避免双方都建立外键字段。
     
对于 一对多 的关系,外键总是建立在多的一方(用到@joincolumn),而 mappedby 存在相反的一方。
比如:
    部门(department)与 员工(employee)
    一个部门对应多个员工。一个员工属于一个部门。
    即部门与员工间的关系是 一对多 的关系。
【举例:】
public class department {
    @onetomany(mappedby = "bookcategory", cascade = cascadetype.all)
    private list<employee> employee;
}

public class employee {
    @manytoone
    private department department;
}

 

8、@onetoone

@onetoone 用于描述两个数据表间 一对一的关联关系。
【属性:】
    cascade,  用于定义级联属性
    fetch,    用于定义 懒加载(lazy,不查询就不加载)、热加载(eager,默认)
    mappedby, 用于定义 被维护的表(相关联的表)
    optional, 用于定义 是否允许对象为 null。

 

三、jpa 实现 crud(以单个实体类为例)

1、搭建环境(以spring boot 2.0为例)

Spring Data JPA学习

 

 

 (1)添加依赖信息

【pom.xml】

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
   xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelversion>4.0.0</modelversion>
   <parent>
      <groupid>org.springframework.boot</groupid>
      <artifactid>spring-boot-starter-parent</artifactid>
      <version>2.2.2.release</version>
      <relativepath/> <!-- lookup parent from repository -->
   </parent>
   <groupid>com.lyh.demo</groupid>
   <artifactid>jpa</artifactid>
   <version>0.0.1-snapshot</version>
   <name>jpa</name>
   <description>jpa demo project for spring boot</description>

   <properties>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <dependency>
         <groupid>org.springframework.boot</groupid>
         <artifactid>spring-boot-starter-data-jpa</artifactid>
      </dependency>

      <dependency>
         <groupid>org.springframework.boot</groupid>
         <artifactid>spring-boot-devtools</artifactid>
         <scope>runtime</scope>
         <optional>true</optional>
      </dependency>
      <dependency>
         <groupid>mysql</groupid>
         <artifactid>mysql-connector-java</artifactid>
         <version>8.0.18</version>
      </dependency>
      <dependency>
         <groupid>org.projectlombok</groupid>
         <artifactid>lombok</artifactid>
         <optional>true</optional>
      </dependency>
      <dependency>
         <groupid>org.springframework.boot</groupid>
         <artifactid>spring-boot-starter-test</artifactid>
         <scope>test</scope>
         <exclusions>
            <exclusion>
               <groupid>org.junit.vintage</groupid>
               <artifactid>junit-vintage-engine</artifactid>
            </exclusion>
         </exclusions>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-maven-plugin</artifactid>
         </plugin>
      </plugins>
   </build>

</project>

 

(2)配置连接

【application.properties】

# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/lyh?useunicode=true&characterencoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.driver

# jpa 配置
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

 

2、编写实体类以及映射关系

【com.lyh.demo.jpa.bean.employee】
package com.lyh.demo.jpa.bean;

import lombok.data;

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

@entity
@table(name = "emp")
@data
@proxy(lazy = false)
public class employee {
    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 32)
    private string name;

    @column(name = "age")
    private integer age;

    @temporal(temporaltype.timestamp)
    private date createdate;
}

 

3、编写dao层

  不需要编写实现类。只需要继承两个接口(jparepository、jpaspecificationexecutor)。

package com.lyh.demo.jpa.dao;

import com.lyh.demo.jpa.bean.employee;
import org.springframework.data.jpa.repository.jparepository;
import org.springframework.data.jpa.repository.jpaspecificationexecutor;
import org.springframework.stereotype.component;

/**
 * jparepository<操作的实体类型, 实体类中主键的类型>, 封装了 crud 基本操作。
 * jpaspecificationexecutor<操作的实体类型>,封装了复杂的操作,比如 分页。
 */
@component
public interface employeedao extends jparepository<employee, integer>, jpaspecificationexecutor<employee> {
}

 

4、编写测试类

【com.lyh.demo.jpa.jpaapplicationtests】

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;

import java.util.date;

@springboottest
class jpaapplicationtests {
   @autowired
   private employeedao employeedao;

   /**
    * 使用 save 方法时,若没有 id,则直接进行 添加操作。
    */
   @test
   void testsave() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);
      employee.setcreatedate(new date());
      employeedao.save(employee);
   }

   /**
    * 使用 save 方法,若存在 id,会先进行一次查询操作,若存在数据,则更新数据,否则保存数据。
    */
   @test
   void testupdate() {
      employee employee = new employee();
      employee.setid(10);
      employee.setname("tom");
      employee.setage((int)(math.random() * 100 + 1));
      employee.setcreatedate(new date());
      employeedao.save(employee);
   }

   /**
    * 根据 id 查询某条数据
    */
   @test
   void testfindone() {
      system.out.println(employeedao.getone(1));
   }

   /**
    * 查询所有的数据
    */
   @test
   void testfindall() {
      system.out.println(employeedao.findall());
   }

   /**
    * 根据id删除数据
    */
   @test
   void testdelete() {
      employeedao.deletebyid(1);
   }
}

测试 save 插入

Spring Data JPA学习

 

 

 

测试 save 更新。

Spring Data JPA学习

 

 

 

Spring Data JPA学习

 

 

 

测试 查询。

Spring Data JPA学习

 

 

 

Spring Data JPA学习

 

 

 

测试删除。

Spring Data JPA学习

 

 

 

5、遇到的坑

(1)执行测试(getone())的时候报错:
  org.hibernate.lazyinitializationexception: could not initialize proxy [com.lyh.demo.jpa.bean.employee#1] - no session

Spring Data JPA学习

 

 

 

原因:

  getone() 内部采用懒加载的方式执行,什么时候用,什么时候才会去触发获取值。

解决办法一:
  在实体类前加上 @proxy(lazy = false) 用于取消懒加载

【即】
package com.lyh.demo.jpa.bean;

import lombok.data;
import org.hibernate.annotations.proxy;

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

@entity
@table(name = "emp")
@data
@proxy(lazy = false)
public class employee {
    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 32)
    private string name;

    @column(name = "age")
    private integer age;

    @temporal(temporaltype.timestamp)
    private date createdate;
}

 

解决方法二:
  在方法执行前,加上 @transactional。

【即】

/**
 * 根据 id 查询某条数据
 */
@test
@transactional
void testfindone() {
   system.out.println(employeedao.getone(2));
}

 

四、jpa 编写sql语句 -- jpql

1、简介

  java persistence query language,可以理解为 jpa 使用的 sql 语句,用于操作实体类以及实体类的属性。

2、使用

(1)在 dao 接口中定义相关方法,并通过 @query 注解来定义 sql 语句。
  需要更新数据时,需要使用 @modifying 注解。测试的时候,需要使用 @transactional 注解。
  若方法参数为实体类对象,则通过 :#{#实体类名.实体类属性名} 获取。且方法参数需要使用 @param声明。

【com.lyh.demo.jpa.dao.employeedao】

package com.lyh.demo.jpa.dao;

import com.lyh.demo.jpa.bean.employee;
import org.springframework.data.jpa.repository.jparepository;
import org.springframework.data.jpa.repository.jpaspecificationexecutor;
import org.springframework.data.jpa.repository.modifying;
import org.springframework.data.jpa.repository.query;
import org.springframework.data.repository.query.param;
import org.springframework.stereotype.component;

import java.util.list;

/**
 * jparepository<操作的实体类型, 实体类中主键的类型>, 封装了 crud 基本操作。
 * jpaspecificationexecutor<操作的实体类型>,封装了复杂的操作,比如 分页。
 * 其中,使用到了方法命名规则写法。
 */
@component
public interface employeedao extends jparepository<employee, integer>, jpaspecificationexecutor<employee> {

    public list<employee> getemployeebyage(integer age);

    @query("from employee where age = ?1")
    public list<employee> getemployeebyage1(integer age);

    public list<employee> getemployeebyageandname(integer age, string name);

    @query("from employee where name = ?2 and age = ?1")
    public list<employee> getemployeebyageandname1(integer age, string name);

    @query("update employee set age = :#{#employee.age} where name = :#{#employee.name}")
    @modifying
    public void updateempagebyname(@param("employee") employee employee);
}

 

(2)测试

【com.lyh.demo.jpa.jpaapplicationtestjsqls】

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;
import org.springframework.test.annotation.rollback;

import javax.transaction.transactional;

@springboottest
class jpaapplicationtestjsqls {
   @autowired
   private employeedao employeedao;

   @test
   void testgetemployeebyage() {
      system.out.println(employeedao.getemployeebyage(40));
   }

   @test
   void testgetemployeebyage1() {
      system.out.println(employeedao.getemployeebyage1(40));
   }

   @test
   void testgetemployeebyageandname() {
      system.out.println(employeedao.getemployeebyageandname(40, "tom"));
   }

   @test
   void testgetemployeebyageandname1() {
      system.out.println(employeedao.getemployeebyageandname1(41, "tom"));
   }

   @test
   @transactional
   void testupdateempagebyname() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(11);
      employeedao.updateempagebyname(employee);
   }
}

 

测试 getemployeebyage

Spring Data JPA学习

 

 

 

测试 getemployeebyage1,与getemployeebyage 的区别在于 getemployeebyage1 是自定义查询方法。

Spring Data JPA学习

 

 

 

测试 getemployeebyageandname

Spring Data JPA学习

 

 

 

测试 getemployeebyageandname1,同样属于自定义查询方法。

Spring Data JPA学习

 

 

 

测试 updateempagebyname,采用对象传参的方式。进行更新操作 需要使用 @modifying 注解。

Spring Data JPA学习

 

 

 

3、遇到的坑

(1)报错:(jdbc style parameters (?) are not supported for jpa queries.)
  org.springframework.beans.factory.beancreationexception: error creating bean with name 'employeedao': invocation of init method failed; nested exception is java.lang.illegalargumentexception: jdbc style parameters (?) are not supported for jpa queries.

Spring Data JPA学习

 

 

 

解决: 在占位符上指定匹配的参数位置(从1开始)

【com.lyh.demo.jpa.dao.employeedao】

@query("from employee where age = ?1")
public list<employee> getemployeebyage1(integer age);

 

(2)使用 实体类对象 作为参数进行 jpql 查询,获取实体类某个参数报错。
  解决办法:使用 :#{#employee.age} 获取参数。

@query("update employee set age = :#{#employee.age} where name = :#{#employee.name}")
@modifying
public void updateempagebyname(@param("employee") employee employee);

 

(3)对于 update、delete 操作,需要使用 @transactional 、 @modifying 注解,否则会报错。


五、jpa 编写 sql 语句 -- sql、方法规则命名查询

1、sql 语法规则

  写法类似于 jpql,需要使用 @query 注解,但是需要使用 nativequery = true。
  若 nativequery = false,则使用 jpql。
  若 nativequery = true,则使用 sql。

Spring Data JPA学习

 

 

 

(1)配置

【application.properties】

# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/lyh?useunicode=true&characterencoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.driver

# jpa 配置
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.mysql5innodbdialect

 

(2)dao层

【com/lyh/demo/jpa/dao/employeedao.java】

package com.lyh.demo.jpa.dao;

import com.lyh.demo.jpa.bean.employee;
import org.springframework.data.jpa.repository.jparepository;
import org.springframework.data.jpa.repository.jpaspecificationexecutor;
import org.springframework.data.jpa.repository.query;
import org.springframework.stereotype.component;

import java.util.list;

@component
public interface employeedao extends jparepository<employee, integer>, jpaspecificationexecutor<employee> {

    @query(value = "select * from emp", nativequery = true)
    public list<employee> getemployee();
}

 

(3)bean
  实体类。

【com/lyh/demo/jpa/bean/employee.java】

package com.lyh.demo.jpa.bean;

import lombok.data;

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

@entity
@table(name = "emp")
@data
public class employee {

    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 32)
    private string name;

    @column(name = "age")
    private integer age;

    @temporal(temporaltype.timestamp)
    private date createtime;
}

 

(4)test

【com/lyh/demo/jpa/jpaapplicationtests.java】

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;

import java.util.date;
import java.util.list;

@springboottest
class jpaapplicationtests {

    @autowired
    private employeedao employeedao;

    @test
    void testsave() {
        employee employee = new employee();
        employee.setname("tom");
        employee.setage(22);
        employee.setcreatetime(new date());
        employeedao.save(employee);
    }

    @test
    void testgetemployee() {
        list<employee> employeelist = employeedao.getemployee();
        for (employee employee: employeelist) {
            system.out.println(employee);
        }
    }
}

 

测试截图:
  执行 两次 testsave() 方法,添加几条测试数据。

Spring Data JPA学习

 

 

 

测试 testgetemployee() 方法,测试 sql 语句。

Spring Data JPA学习

 

 

 

2、方法命名规则查询

  是对 jpql 的进一步封装。只需要根据 springdatajpa 提供的方法名规则去定义方法名,从而不需要配置 jpql 语句,会自动根据方法名去解析成 sql 语句。
(1)关键字定义:
  https://docs.spring.io/spring-data/jpa/docs/2.2.3.release/reference/html/#repository-query-keywords
详细文档:
  https://blog.csdn.net/qq_32448349/article/details/89445216

Spring Data JPA学习

 

 

 

(2)举例:

findemployeesbyageandname 等价于 select * from emp where age = ? and name = ?
根据属性名称进行查询。

findemployeesbynamelike 等价于 select * from emp where name like ?
根据属性进行模糊查询

 

(3)测试:

【com/lyh/demo/jpa/dao/employeedao.java】

package com.lyh.demo.jpa.dao;

import com.lyh.demo.jpa.bean.employee;
import org.springframework.data.jpa.repository.jparepository;
import org.springframework.data.jpa.repository.jpaspecificationexecutor;
import org.springframework.data.jpa.repository.query;
import org.springframework.stereotype.component;

import java.util.list;

@component
public interface employeedao extends jparepository<employee, integer>, jpaspecificationexecutor<employee> {

    @query(value = "select * from emp", nativequery = true)
    public list<employee> getemployee();

    public list<employee> findemployeesbyageandname(integer age, string name);

    public list<employee> findemployeesbynamelike(string name);
}



【com/lyh/demo/jpa/jpaapplicationtests.java】

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;

import java.util.date;
import java.util.list;

@springboottest
class jpaapplicationtests {

    @autowired
    private employeedao employeedao;

    @test
    void testsave() {
        employee employee = new employee();
        employee.setname("tom");
        employee.setage(22);
        employee.setcreatetime(new date());
        employeedao.save(employee);
    }

    @test
    void testgetemployee() {
        list<employee> employeelist = employeedao.getemployee();
        for (employee employee: employeelist) {
            system.out.println(employee);
        }
    }

    @test
    void testfindemployeesbyageandname() {
        list<employee> employeelist = employeedao.findemployeesbyageandname(22, "tom");
        for (employee employee: employeelist) {
            system.out.println(employee);
        }
    }

    @test
    void testfindemployeesbynamelike() {
        list<employee> employeelist = employeedao.findemployeesbynamelike("t%");
        for (employee employee: employeelist) {
            system.out.println(employee);
        }
    }
}

基本查询:

Spring Data JPA学习

 

 

 

模糊查询:

Spring Data JPA学习

 

 

 

六、动态查询(jpaspecificationexecutor、specification)

1、jpaspecificationexecutor

  jpaspecificationexecutor 是一个接口。查询语句都定义在 specification 中。

package org.springframework.data.jpa.repository;

import java.util.list;
import java.util.optional;
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;
import org.springframework.lang.nullable;

public interface jpaspecificationexecutor<t> {
    // 查询单个对象
  optional<t> findone(@nullable specification<t> var1);

    // 查询对象列表
  list<t> findall(@nullable specification<t> var1);

    // 查询对象列表,并返回分页数据
  page<t> findall(@nullable specification<t> var1, pageable var2);

    // 查询对象列表,并排序
  list<t> findall(@nullable specification<t> var1, sort var2);

    // 统计查询的结果
  long count(@nullable specification<t> var1);
}

 

2、specification

  定义 sql 语句。同样是一个接口,需要自定义实现类。需要重写 topredicate() 方法。

// root 指查询的根对象,可以获取任何属性。
// criteriaquery 标准查询,可以自定义查询方式(一般不用)
// criteriabuilder 指查询的构造器,封装了很多查询条件
predicate topredicate(root<t> var1, criteriaquery<?> var2, criteriabuilder var3);

 

package org.springframework.data.jpa.domain;

import java.io.serializable;
import javax.persistence.criteria.criteriabuilder;
import javax.persistence.criteria.criteriaquery;
import javax.persistence.criteria.predicate;
import javax.persistence.criteria.root;
import org.springframework.lang.nullable;

public interface specification<t> extends serializable {
  long serialversionuid = 1l;

  static <t> specification<t> not(@nullable specification<t> spec) {
    return spec == null ? (root, query, builder) -> {
      return null;
    } : (root, query, builder) -> {
      return builder.not(spec.topredicate(root, query, builder));
    };
  }

  @nullable
  static <t> specification<t> where(@nullable specification<t> spec) {
    return spec == null ? (root, query, builder) -> {
      return null;
    } : spec;
  }

  @nullable
  default specification<t> and(@nullable specification<t> other) {
    return specificationcomposition.composed(this, other, (builder, left, rhs) -> {
      return builder.and(left, rhs);
    });
  }

  @nullable
  default specification<t> or(@nullable specification<t> other) {
    return specificationcomposition.composed(this, other, (builder, left, rhs) -> {
      return builder.or(left, rhs);
    });
  }

  @nullable
  predicate topredicate(root<t> var1, criteriaquery<?> var2, criteriabuilder var3);
}

 

3、基本使用

(1)步骤:
  step1:实现 specification 接口(定义泛型,为查询的对象类型),重写 topredicate() 方法。
  step2:定义 criteriabuilder 查询条件。

(2)普通查询

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;
import org.springframework.data.jpa.domain.specification;

import javax.persistence.criteria.*;
import java.util.date;
import java.util.list;

@springboottest
class jpaapplicationtests {

   @autowired
   private employeedao employeedao;

   @test
   void testsave() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);
      employee.setcreatetime(new date());
      employeedao.save(employee);
   }


   @test
   void testspecification() {
      // 定义内部类,泛型为 查询的对象
      specification<employee> specification = new

         specification<employee>() {

            @override
            public predicate topredicate(root root, criteriaquery criteriaquery, criteriabuilder criteriabuilder) {
               // 获取比较的属性
               path<object> name = root.get("name");
               // 构建查询条件, select * from emp where name = "tom";
               predicate predicate = criteriabuilder.equal(name, "tom");
               return predicate;
            }


         };
      list<employee> employeelist = employeedao.findall(specification);
      for (employee employee : employeelist) {
         system.out.println(employee);
      }
   }
}

 

Spring Data JPA学习

 

 

 

(3)多条件拼接、模糊查询

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;
import org.springframework.data.jpa.domain.specification;

import javax.persistence.criteria.*;
import java.util.date;
import java.util.list;

@springboottest
class jpaapplicationtests {

   @autowired
   private employeedao employeedao;

   @test
   void testsave() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);
      employee.setcreatetime(new date());
      employeedao.save(employee);
   }


   @test
   void testspecification() {
      // 定义内部类,泛型为 查询的对象
      specification<employee> specification = new
         specification<employee>() {
            @override
            public predicate topredicate(root root, criteriaquery criteriaquery, criteriabuilder criteriabuilder) {
               // 获取比较的属性
               path<string> name = root.get("name");
               path<integer> age = root.get("age");

               // 构建查询条件, select * from emp where name like "to%" and age >= 22;
               predicate predicate1 = criteriabuilder.like(name, "to%");
               predicate predicate2 = criteriabuilder.ge(age, 22);
               predicate predicate = criteriabuilder.and(predicate1, predicate2);
               return predicate;
            }
         };
      list<employee> employeelist = employeedao.findall(specification);
      for (employee employee : employeelist) {
         system.out.println(employee);
      }
   }
}

 

Spring Data JPA学习

 

 

 

(4)排序
  在上例 多条件拼接 代码的基础上增加排序,使数据按照 id 降序输出。

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;
import org.springframework.data.domain.sort;
import org.springframework.data.jpa.domain.specification;

import javax.persistence.criteria.*;
import java.util.date;
import java.util.list;

@springboottest
class jpaapplicationtests {

   @autowired
   private employeedao employeedao;

   @test
   void testsave() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);
      employee.setcreatetime(new date());
      employeedao.save(employee);
   }


   @test
   void testspecification() {
      // 定义内部类,泛型为 查询的对象
      specification<employee> specification = new

         specification<employee>() {

            @override
            public predicate topredicate(root root, criteriaquery criteriaquery, criteriabuilder criteriabuilder) {
               // 获取比较的属性
               path<string> name = root.get("name");
               path<integer> age = root.get("age");

               // 构建查询条件, select * from emp where name like "to%" and age >= 22;
               predicate predicate1 = criteriabuilder.like(name, "to%");
               predicate predicate2 = criteriabuilder.ge(age, 22);
               predicate predicate = criteriabuilder.and(predicate1, predicate2);
               return predicate;
            }


         };
      // 定义排序(sort.direction.desc,降序; sort.direction.asc,升序)
      sort sort = sort.by(sort.direction.desc, "id");
      list<employee> employeelist = employeedao.findall(specification, sort);
      for (employee employee : employeelist) {
         system.out.println(employee);
      }
   }
}

 

Spring Data JPA学习

 

 

 

(5)分页
  在上例 多条件拼接 代码的基础上增加分页。如下例,按每页一条数据分页,取第2页数据。

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;
import org.springframework.data.domain.page;
import org.springframework.data.domain.pagerequest;
import org.springframework.data.domain.pageable;
import org.springframework.data.jpa.domain.specification;

import javax.persistence.criteria.*;
import java.util.date;

@springboottest
class jpaapplicationtests {

   @autowired
   private employeedao employeedao;

   @test
   void testsave() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);
      employee.setcreatetime(new date());
      employeedao.save(employee);
   }


   @test
   void testspecification() {
      // 定义内部类,泛型为 查询的对象
      specification<employee> specification = new

         specification<employee>() {

            @override
            public predicate topredicate(root root, criteriaquery criteriaquery, criteriabuilder criteriabuilder) {
               // 获取比较的属性
               path<string> name = root.get("name");
               path<integer> age = root.get("age");

               // 构建查询条件, select * from emp where name like "to%" and age >= 22;
               predicate predicate1 = criteriabuilder.like(name, "to%");
               predicate predicate2 = criteriabuilder.ge(age, 22);
               predicate predicate = criteriabuilder.and(predicate1, predicate2);
               return predicate;
            }


         };
      // 定义分页,其中 第一个参数指的是 当前查询的页数(从0开始),第二个参数指的是每页的数量
      pageable pageable = pagerequest.of(1, 1);
      page<employee> page = employeedao.findall(specification, pageable);
      // 获取当前查询数据的集合
      system.out.println(page.getcontent());
      // 获取总条数
      system.out.println(page.gettotalelements());
      // 获取总页数
      system.out.println(page.gettotalpages());
   }
}

 

Spring Data JPA学习

 

 

 

七、多表操作

1、一对一(@onetoone)

  表的某条数据,对应另外一张表的某条数据。

2、一对多(@onetomany,@manytoone)

(1)基本概念:
  表的某条数据,对应另外一张表的多条数据。
  将 “一” 的一方称为 :主表。
  将 “多” 的一方称为 :从表。
  通常将 外键 置于从表上,即 从表上增加一列作为外键,并依赖于主表的某列。

(2)sql 语句建表

【举例:】
员工与部门间的关系。
一个部门可以有多个员工,而一个员工属于一个部门。此时部门与员工间为 一对多 的关系。
部门表为主表,员工表为从表。外键建立在 员工表(从表)上。

create table dept (
    deptid int primary key auto_increment,
    deptname varchar(20)
);

create table emp (
    id int primary key auto_increment,
    name varchar(32),
    age int,
    deptid int,
    foreign key(deptid) references dept(deptid)
);

 

(3)jpa建表

【步骤:】
step1:明确两表之间的关系
step2:确定表之间的关系,一对多(外键)还是多对多(中间表)关系。
step3:编写实体类,在实体类中建立表关系(声明相应的属性)。
step4:配置映射关系

 

step1、step2:
  部门表 与 员工表间 属于 一对多的关系,所以需要在员工表上建立外键。

【com/lyh/demo/jpa/bean/employee.java】
package com.lyh.demo.jpa.bean;

import lombok.data;

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

@entity
@table(name = "emp")
@data
public class employee {

    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 32)
    private string name;

    @column(name = "age")
    private integer age;
}

【com/lyh/demo/jpa/bean/department.java】
package com.lyh.demo.jpa.bean;

import lombok.data;

import javax.persistence.*;

@entity
@table(name = "dept")
@data
public class department {
   @id
   @generatedvalue(strategy = generationtype.identity)
   private int deptid;
   private string deptname;
}

 

step3、step4:

  在实体类间建立联系,并添加映射关系。

【com/lyh/demo/jpa/bean/employee.java】
package com.lyh.demo.jpa.bean;

import lombok.data;

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

@entity
@table(name = "emp")
@data
public class employee {

    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 32)
    private string name;

    @column(name = "age")
    private integer age;

    /**
     * 员工表与部门表间 属于 多对一的关系。所以在员工类中应定义 一个普通属性去保存部门信息。
     * 并使用 @manytoone 去定义映射关系(多对一).
     * 使用 @joincolumn 定义外键(在从表上定义,name指的是 外键名,referencedcolumnname指的是依赖的主表的主键)。
     */
    @manytoone(targetentity = department.class)
    @joincolumn(name = "deptid", referencedcolumnname = "deptid")
    private department department;
}



【com/lyh/demo/jpa/bean/department.java】
package com.lyh.demo.jpa.bean;

import lombok.data;

import javax.persistence.*;
import java.util.hashset;
import java.util.set;

@entity
@table(name = "dept")
@data
public class department {
   @id
   @generatedvalue(strategy = generationtype.identity)
   private int deptid;
   private string deptname;

   /**
    * 部门表与员工表是一对多的关系,所以部门实体类中 应定义集合 去保存员工信息。
    * 并使用 @onetomany 去指定映射关系(一对多)。
    * 可以使用 @joincolumn 去建立外键,此时可以对外键进行维护(一的一方),若对此外键赋值,相对于多的一方,会多出一条 update。
    * 若放弃外键维护,可以使用 mapperby 指定关联关系,其值为对应的类维护的属性名称。
    */
// @onetomany(targetentity = employee.class)
// @joincolumn(name = "id", referencedcolumnname = "deptid")
   @onetomany(mappedby = "department")
   private set<employee> employees = new hashset<employee>();
}

 

(4)测试

  文件结构如下图:

Spring Data JPA学习

 

 代码:

【application.properties】

# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/lyh?useunicode=true&characterencoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.driver

# jpa 配置
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.mysql5innodbdialect


【com/lyh/demo/jpa/bean/department.java】

package com.lyh.demo.jpa.bean;

import lombok.data;

import javax.persistence.*;
import java.util.hashset;
import java.util.set;

@entity
@table(name = "dept")
@data
public class department {
   @id
   @generatedvalue(strategy = generationtype.identity)
   private int deptid;
   private string deptname;

   /**
    * 部门表与员工表是一对多的关系,所以部门实体类中 应定义集合 去保存员工信息。
    * 并使用 @onetomany 去指定映射关系(一对多)。
    * 可以使用 @joincolumn 去建立外键,此时可以对外键进行维护(一的一方),若对此外键赋值,相对于多的一方,会多出一条 update。
    * 若放弃外键维护,可以使用 mapperby 指定关联关系,其值为对应的类维护的属性名称。
    */
// @onetomany(targetentity = employee.class)
// @joincolumn(name = "id", referencedcolumnname = "deptid")
   @onetomany(mappedby = "department")
   private set<employee> employees = new hashset<employee>();
}


【com/lyh/demo/jpa/bean/employee.java】

package com.lyh.demo.jpa.bean;

import lombok.data;

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

@entity
@table(name = "emp")
@data
public class employee {

    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 32)
    private string name;

    @column(name = "age")
    private integer age;

    /**
     * 员工表与部门表间 属于 多对一的关系。所以在员工类中应定义 一个普通属性去保存部门信息。
     * 并使用 @manytoone 去定义映射关系(多对一).
     * 使用 @joincolumn 定义外键(在从表上定义,name指的是 外键名,referencedcolumnname指的是依赖的主表的主键)。
     */
    @manytoone(targetentity = department.class)
    @joincolumn(name = "deptid", referencedcolumnname = "deptid")
    private department department;
}


【com/lyh/demo/jpa/dao/departmentdao.java】

package com.lyh.demo.jpa.dao;

import com.lyh.demo.jpa.bean.department;
import org.springframework.data.jpa.repository.jparepository;
import org.springframework.data.jpa.repository.jpaspecificationexecutor;
import org.springframework.stereotype.component;

@component
public interface departmentdao extends jparepository<department, integer>, jpaspecificationexecutor<department> {
}


【com/lyh/demo/jpa/dao/employeedao.java】

package com.lyh.demo.jpa.dao;

import com.lyh.demo.jpa.bean.employee;
import org.springframework.data.jpa.repository.jparepository;
import org.springframework.data.jpa.repository.jpaspecificationexecutor;
import org.springframework.data.jpa.repository.query;
import org.springframework.stereotype.component;

import java.util.list;

@component
public interface employeedao extends jparepository<employee, integer>, jpaspecificationexecutor<employee> {
}


【com/lyh/demo/jpa/jpaapplicationtests.java】

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.department;
import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.departmentdao;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;
import org.springframework.data.domain.page;
import org.springframework.data.domain.pagerequest;
import org.springframework.data.domain.pageable;
import org.springframework.data.jpa.domain.specification;
import org.springframework.transaction.annotation.transactional;

import javax.persistence.criteria.*;
import java.util.date;

@springboottest
class jpaapplicationtests {

   @autowired
   private employeedao employeedao;

   @autowired
   private departmentdao departmentdao;

   @test
   void testsave1() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);

      department department = new department();
      department.setdeptid(1);
      department.setdeptname("开发");

      employeedao.save(employee);
      departmentdao.save(department);
   }

   @test
   void testsave2() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);

      department department = new department();
      department.setdeptid(1);
      department.setdeptname("开发");

      // 维护外键,即添加值(此处执行顺序可能会导致出错)
      employee.setdepartment(department);
      // departmentdao.save(department);
      employeedao.save(employee);
      departmentdao.save(department);
   }
}

 

测试截图:

  测试 testsave1(),由于没有维护外键,所以外键为 null。

Spring Data JPA学习

 

 

测试 testsave2(),维护外键,外键有值。

Spring Data JPA学习

 

 

(5)级联操作

  注意,上例操作,需要对每个表进行一次操作,这样有时候会很繁琐。

  此时级联就可以派上用场了,级联用于 操作一个实体类的同时 操作其关联的另一个实体类。

  上例 testsave2() 可能会出现的问题:当数据为空时,由于先执行了 employeedao.save(employee);

   再执行的 departmentdao.save(department); 此时由于 主表没有数据, 从表添加外键会出错。

Spring Data JPA学习

 

 

解决方法一:

  调换执行 sql 的顺序。

Spring Data JPA学习

 

 

解决方法二:

  采用级联属性(cascade = cascadetype.all)。

  修改上例代码。

【com/lyh/demo/jpa/bean/department.java】
package com.lyh.demo.jpa.bean;

import lombok.data;

import javax.persistence.*;
import java.util.hashset;
import java.util.set;

@entity
@table(name = "dept")
public class department {
   @id
   @generatedvalue(strategy = generationtype.identity)
   private int deptid;
   private string deptname;

   /**
    * 部门表与员工表是一对多的关系,所以部门实体类中 应定义集合 去保存员工信息。
    * 并使用 @onetomany 去指定映射关系(一对多)。
    * 可以使用 @joincolumn 去建立外键,此时可以对外键进行维护(一的一方),若对此外键赋值,相对于多的一方,会多出一条 update。
    * 若放弃外键维护,可以使用 mapperby 指定关联关系,其值为对应的类维护的属性名称。
    * 使用 cascade 用于定义级联属性。
    */
// @onetomany(targetentity = employee.class)
// @joincolumn(name = "id", referencedcolumnname = "deptid")
   @onetomany(mappedby = "department", cascade = cascadetype.all)
   private set<employee> employees = new hashset<employee>();

   public int getdeptid() {
      return deptid;
   }

   public void setdeptid(int deptid) {
      this.deptid = deptid;
   }

   public string getdeptname() {
      return deptname;
   }

   public void setdeptname(string deptname) {
      this.deptname = deptname;
   }

   public set<employee> getemployees() {
      return employees;
   }

   public void setemployees(set<employee> employees) {
      this.employees = employees;
   }
}



【com/lyh/demo/jpa/jpaapplicationtests.java】

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.department;
import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.departmentdao;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;
import org.springframework.data.domain.page;
import org.springframework.data.domain.pagerequest;
import org.springframework.data.domain.pageable;
import org.springframework.data.jpa.domain.specification;
import org.springframework.transaction.annotation.transactional;

import javax.persistence.criteria.*;
import java.util.date;

@springboottest
class jpaapplicationtests {

   @autowired
   private employeedao employeedao;

   @autowired
   private departmentdao departmentdao;

   @test
   void testsave1() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);

      department department = new department();
      department.setdeptid(1);
      department.setdeptname("开发");

      employeedao.save(employee);
      departmentdao.save(department);
   }

   @test
   void testsave2() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);

      department department = new department();
      department.setdeptid(1);
      department.setdeptname("开发");

      // 维护外键,即添加值
      employee.setdepartment(department);
      department.getemployees().add(employee);
      departmentdao.save(department);
   }
}

Spring Data JPA学习

 

 

注:

  使用级联遇到的坑(堆栈溢出 java.lang.*error)。去除 @data,手动 getter、setter。或者重写 tostring() 方法,让其不输出 外键关联的属性。

Spring Data JPA学习

 

 

(6)对象导航查询

  通过查询一个对象,可以查询到其关联的对象。

  对于 一对多 关系,若从 一 的对象 去 查询 多的对象,则默认采用延迟加载的形式。

  若从 多 的对象 去 查询 一的对象,则默认采用立即加载的形式。

对上例代码进行修改。
【com/lyh/demo/jpa/bean/department.java】

package com.lyh.demo.jpa.bean;

import lombok.data;

import javax.persistence.*;
import java.util.hashset;
import java.util.set;

@entity
@table(name = "dept")
public class department {
   @id
   @generatedvalue(strategy = generationtype.identity)
   private int deptid;
   private string deptname;

   /**
    * 部门表与员工表是一对多的关系,所以部门实体类中 应定义集合 去保存员工信息。
    * 并使用 @onetomany 去指定映射关系(一对多)。
    * 可以使用 @joincolumn 去建立外键,此时可以对外键进行维护(一的一方),若对此外键赋值,相对于多的一方,会多出一条 update。
    * 若放弃外键维护,可以使用 mapperby 指定关联关系,其值为对应的类维护的属性名称。
    * 使用 cascade 用于定义级联属性。
    */
// @onetomany(targetentity = employee.class)
// @joincolumn(name = "id", referencedcolumnname = "deptid")
   @onetomany(mappedby = "department", cascade = cascadetype.all)
   private set<employee> employees = new hashset<employee>();

   public int getdeptid() {
      return deptid;
   }

   public void setdeptid(int deptid) {
      this.deptid = deptid;
   }

   public string getdeptname() {
      return deptname;
   }

   public void setdeptname(string deptname) {
      this.deptname = deptname;
   }

   public set<employee> getemployees() {
      return employees;
   }

   public void setemployees(set<employee> employees) {
      this.employees = employees;
   }

   @override
   public string tostring() {
      return "department{" +
         "deptid=" + deptid +
         ", deptname='" + deptname +
         '}';
   }
}


【com/lyh/demo/jpa/jpaapplicationtests.java】
package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.department;
import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.dao.departmentdao;
import com.lyh.demo.jpa.dao.employeedao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;
import org.springframework.transaction.annotation.transactional;

@springboottest
class jpaapplicationtests {

   @autowired
   private employeedao employeedao;
   @autowired
   private departmentdao departmentdao;

   /**
    * 测试级联添加数据
    */
   @test
   void testsave() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);

      department department = new department();
      department.setdeptid(1);
      department.setdeptname("开发");

      // 维护外键,即添加值
      employee.setdepartment(department);
      department.getemployees().add(employee);
      departmentdao.save(department);
   }


   /**
    * 测试对象查询(获取多 的一方的对象,并获取其关联的对象。其默认加载方式为 立即加载。)
    */
   @test
   @transactional
   void testobjectqueryonefrommany() {
      employee employee = employeedao.getone(1);
      system.out.println(employee.getdepartment());
   }


   /**
    * 测试对象查询(获取一 的一方的对象,并获取其关联的对象。其默认加载方式为 延迟加载。)
    */
   @test
   @transactional
   void testobjectquerymanyfromone() {
      department department = departmentdao.getone(1);
      system.out.println(department.getemployees());
   }
}

 

测试  testobjectqueryonetomany()。

Spring Data JPA学习

 

 

测试 testobjectquerymanyfromone()。

Spring Data JPA学习

 

 

3、多对多(@manytomany)

(1)基本概念:

  两张表之间互为一对多的关系。

  采用中间表来维护 两表间的关系。中间表至少由两个字段组成,且这两个字段作为外键指向两张表的主键,形成联合主键。

 

(2)sql 建表

  类似于 一对多关系。

【举例:】
    员工表 与 角色表。
    一个员工可以对应多个角色,一个角色可以对应多个员工。员工与角色之间是多对多关系。
    需要建立中间表。
    
drop table emp_and_role;
drop table emp;
drop table role;
create table role (
    roleid int primary key auto_increment,
    rolename varchar(32) 
);

create table emp (
    id int primary key auto_increment,
    name varchar(32),
    age int
);

create table emp_and_role (
    emp_id int,
    role_id int,
    primary key(emp_id, role_id),
    foreign key(emp_id) references emp(id),
    foreign key(role_id) references role(roleid)
);

 

(3)jpa 建表

【步骤:】
step1:明确两表之间的关系
step2:确定表之间的关系,一对多(外键)还是多对多(中间表)关系。
step3:编写实体类,在实体类中建立表关系(声明相应的属性)。
step4:配置映射关系

 

step1、step2:

  员工表、角色表为多对多关系,所以需建立中间表。

【com/lyh/demo/jpa/bean/employee.java】
package com.lyh.demo.jpa.bean;

import lombok.data;

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

@entity
@table(name = "emp")
@data
public class employee {

    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 32)
    private string name;

    @column(name = "age")
    private integer age;
}


【com/lyh/demo/jpa/bean/role.java】

package com.lyh.demo.jpa.bean;

import lombok.data;

import javax.persistence.*;

@entity
@table(name = "role")
@data
public class role {
   @id
   @generatedvalue(strategy = generationtype.identity)
   private integer roleid;

   @column(length = 32)
   private string rolename;
}


【com/lyh/demo/jpa/dao/employeedao.java】

package com.lyh.demo.jpa.dao;

import com.lyh.demo.jpa.bean.employee;
import org.springframework.data.jpa.repository.jparepository;
import org.springframework.data.jpa.repository.jpaspecificationexecutor;
import org.springframework.stereotype.component;

@component
public interface employeedao extends jparepository<employee, integer>, jpaspecificationexecutor<employee> {
}


【com/lyh/demo/jpa/dao/roledao.java】

package com.lyh.demo.jpa.dao;

import com.lyh.demo.jpa.bean.role;
import org.springframework.data.jpa.repository.jparepository;
import org.springframework.data.jpa.repository.jpaspecificationexecutor;
import org.springframework.stereotype.component;

@component
public interface roledao extends jparepository<role, integer>, jpaspecificationexecutor<role> {
}

 

step3、step4:

  配置映射关系。

【com/lyh/demo/jpa/bean/employee.java】

package com.lyh.demo.jpa.bean;

import lombok.data;

import javax.persistence.*;
import java.util.date;
import java.util.hashset;
import java.util.set;

@entity
@table(name = "emp")
@data
public class employee {

    @id
    @generatedvalue(strategy = generationtype.identity)
    private integer id;

    @column(name = "name", length = 32)
    private string name;

    @column(name = "age")
    private integer age;

    /**
     * 配置多对多关系。
     * @jointable 为配置中间表。
     * 其中:
     *  name:中间表名。
     *  joincolumns:定义外键,并关联于当前类的主键。
     *  inversejoincolumns:定义外键,并关联于另一个类的主键。
     */
    @manytomany(targetentity = role.class)
    @jointable(name = "emp_and_role",
    joincolumns = {@joincolumn(name = "emp_id", referencedcolumnname = "id")},
    inversejoincolumns = {@joincolumn(name = "role_id", referencedcolumnname = "roleid")})
    private set<role> roleset = new hashset<>();
}


【com/lyh/demo/jpa/bean/role.java】

package com.lyh.demo.jpa.bean;

import lombok.data;

import javax.persistence.*;
import java.util.hashset;
import java.util.set;

@entity
@table(name = "role")
public class role {
   @id
   @generatedvalue(strategy = generationtype.identity)
   private integer roleid;

   @column(length = 32)
   private string rolename;

   /**
    * 放弃外键维护权。
    * 并定义级联属性。
    */
   @manytomany(mappedby = "roleset", cascade = cascadetype.all)
   private set<employee> employeeset = new hashset<>();

   public integer getroleid() {
      return roleid;
   }

   public void setroleid(integer roleid) {
      this.roleid = roleid;
   }

   public string getrolename() {
      return rolename;
   }

   public void setrolename(string rolename) {
      this.rolename = rolename;
   }

   public set<employee> getemployeeset() {
      return employeeset;
   }

   public void setemployeeset(set<employee> employeeset) {
      this.employeeset = employeeset;
   }

   @override
   public string tostring() {
      return "role{" +
         "roleid=" + roleid +
         ", rolename='" + rolename + '\'' +
         ", employeeset=" + employeeset +
         '}';
   }
}

 

(4)测试(使用级联赋值)

【com/lyh/demo/jpa/jpaapplicationtests.java】

package com.lyh.demo.jpa;

import com.lyh.demo.jpa.bean.employee;
import com.lyh.demo.jpa.bean.role;
import com.lyh.demo.jpa.dao.employeedao;
import com.lyh.demo.jpa.dao.roledao;
import org.junit.jupiter.api.test;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.boot.test.context.springboottest;

@springboottest
class jpaapplicationtests {

   @autowired
   private employeedao employeedao;

   @autowired
   private roledao roledao;

   @test
   void testsave1() {
      employee employee = new employee();
      employee.setname("tom");
      employee.setage(22);

      role role = new role();
      role.setrolename("经理");

      // 维护外键
      employee.getroleset().add(role);
      role.getemployeeset().add(employee);
      // 使用级联赋值
      roledao.save(role);
   }
}

 

Spring Data JPA学习