累成一条狗

Spring Data JPA学习

一、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;
}

 

 

 

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

 

 

 

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

 

 

 

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

 

 

 (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 插入

 

 

 

测试 save 更新。

 

 

 

 

 

 

测试 查询。

 

 

 

 

 

 

测试删除。

 

 

 

5、遇到的坑

(1)执行测试(getOne())的时候报错:
  org.hibernate.LazyInitializationException: could not initialize proxy [com.lyh.demo.jpa.bean.Employee#1] - no Session

 

 

 

原因:

  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

 

 

 

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

 

 

 

测试 getEmployeeByAgeAndName

 

 

 

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

 

 

 

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

 

 

 

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.

 

 

 

解决: 在占位符上指定匹配的参数位置(从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。

 

 

 

(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() 方法,添加几条测试数据。

 

 

 

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

 

 

 

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

 

 

 

(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);
        }
    }
}

基本查询:

 

 

 

模糊查询:

 

 

 

六、动态查询(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);
      }
   }
}

 

 

 

 

(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);
      }
   }
}

 

 

 

 

(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);
      }
   }
}

 

 

 

 

(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());
   }
}

 

 

 

 

七、多表操作

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

  文件结构如下图:

 

 代码:

【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。

 

 

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

 

 

(5)级联操作

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

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

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

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

 

 

解决方法一:

  调换执行 sql 的顺序。

 

 

解决方法二:

  采用级联属性(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);
   }
}

 

 

注:

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

 

 

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

 

 

测试 testObjectQueryManyFromOne()。

 

 

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

 

posted on 2019-12-31 23:34  累成一条狗  阅读(1973)  评论(0编辑  收藏  举报

导航