Spring Data JPA學習

1、Spring Data JPA

一、簡介

(1)官網地址:
  https://spring.io/projects/spring-data-jpa
參考文檔:
  https://docs.spring.io/spring-data/jpa/docs/2.2.3.RELEASE/reference/html/#prefacehtml

(2)基本介紹:
  Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範封裝的一套 JPA 框架。使開發者經過極簡的代碼實現對數據庫的訪問和操做。
注:
  ORM 框架:指的是 Object Relational Mapping,即對象關係映射。採用元數據來描述對象和關係映射的細節。
  元數據:通常採用 XML 文件的形式。常見 ORM 框架如:mybatis、Hibernate。
  JPA:指的是 Java Persistence API,即 Java 持久層 API。經過 xml 或註解的映射關係將運行期的實體對象持久化到數據庫中。java

二、sping boot 項目中使用

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

【pom.xml】

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

 

(2)在 application.properties 中配置spring

【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

 

2、基本註解

一、@Entity

@Entity 寫在類上,用於指明一個類與數據庫表相對應。
    屬性:
        name,可選,用於自定義映射的表名。若沒有,則默認以類名爲表名。

【舉例1:默認類名爲表名】
import javax.persistence.Entity;

@Entity
public class Blog {
}

【舉例2:自定義表名】
import javax.persistence.Entity;

@Entity(name="t_blog")
public class Blog {
}

 

二、@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 爲主。

 

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

 

 

 

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

 

 

 

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

 

 

 

六、級聯(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      去除外鍵關聯,當刪一個實體時,存在外鍵沒法刪除,使用此級聯能夠去除外鍵。

 

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

 

八、@OneToOne

@OneToOne 用於描述兩個數據表間 一對一的關聯關係。
【屬性:】
    cascade,  用於定義級聯屬性
    fetch,    用於定義 懶加載(LAZY,不查詢就不加載)、熱加載(EAGER,默認)
    mappedBy, 用於定義 被維護的表(相關聯的表)
    optional, 用於定義 是否容許對象爲 null

 

3、JPA 實現 CRUD(以單個實體類爲例)

一、搭建環境(以Spring Boot 2.0爲例)

 

 

 (1)添加依賴信息sql

【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

 

二、編寫實體類以及映射關係

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

 

三、編寫Dao層

  不須要編寫實現類。只須要繼承兩個接口(JpaRepository、JpaSpecificationExecutor)。apache

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

 

四、編寫測試類

【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 插入api

 

 

 

測試 save 更新。mybatis

 

 

 

 

 

 

測試 查詢。oracle

 

 

 

 

 

 

測試刪除。

 

 

 

五、遇到的坑

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

 

4、JPA 編寫sql語句 -- jpql

一、簡介

  Java Persistence Query Language,能夠理解爲 JPA 使用的 sql 語句,用於操做實體類以及實體類的屬性。

二、使用

(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 註解。

 

 

 

三、遇到的坑

(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 註解,不然會報錯。


5、JPA 編寫 sql 語句 -- sql、方法規則命名查詢

一、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 語句。

 

 

 

二、方法命名規則查詢

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

基本查詢:

 

 

 

模糊查詢:

 

 

 

6、動態查詢(JpaSpecificationExecutor、Specification)

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

 

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

 

三、基本使用

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

 

 

 

 

7、多表操做

一、一對一(@OneToOne)

  表的某條數據,對應另一張表的某條數據。

二、一對多(@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:配置映射關係

 

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

 

Step三、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()。

 

 

三、多對多(@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:配置映射關係

 

Step一、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> {
}

 

Step三、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);
   }
}

 

相關文章
相關標籤/搜索