Spring Data JPA實現多表的關聯查詢

1、Spring Data JPA關係映射

對象關係映射(Object relational mapping)是指通過將對象狀態映射到數據庫列,來開發和維護對象和關係數據庫之間的關係。它能夠輕鬆處理(執行)各種數據庫操作,如插入、更新、刪除等。

關係映射的註解:

註解 說明
@JoinColumn 指定一個實體組織或實體集合。用在“多對一”和“一對多”的關聯中。
@OneToOne 定義表之間“一對一”的關係。
@OneToMany 定義表之間“一對多”的關係。
@ManyToOne 定義表之間“多對一”的關係。
@ManyToMany 定義表之間“多對多”的關係。

下面將介紹如何使用Spring Data JPA處理多張數據庫表之間的關聯關係。

 

2、創建項目與配置信息

(1)創建SpringBoot項目

創建SpringBoot項目,並創建項目結構:dao(數據訪問層)、entity(實體層)、test(測試層)。

(2)添加JPA和MySQL數據庫的依賴

在pom.xml配置文件中,添加如下依賴:

<!-- Spring Data JPA -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
 
<!-- MySQL的JDBC數據庫驅動 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>

(3)配置數據庫連接信息

SpringBoot項目使用MySQL等關係型數據庫,需要配置連接信息。

將默認的application.properties文件的後綴修改爲“.yml”,即配置文件名稱爲:application.yml,並配置以下MySQL數據庫的連接信息:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_admin?useSSL=false&amp
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    database: MYSQL
    show-sql: true
    open-in-view: true
    properties:
      hibernate:
        enable_lazy_load_no_trans: true #使用延時加載時控制Session的生命週期
        dialect: org.hibernate.dialect.MySQL5Dialect
        ddl-auto: update

 

3、延時加載時控制Session的生命週期

在Spring Data JPA中在使用延時加載時,要控制Session的生命週期,否則會出現“could not initialize proxy [xxxxxx#1] - no Session”錯誤。可以在配置文件中配置以下代碼來控制Session的生命週期:

application.properties配置文件:

spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

application.yml配置文件:

 

4、一對一查詢

一對一外鍵關聯的配置比較簡單,以公民實體對象和身份證實體對象爲例,在表tab_people(公民表)中添加一個字段“card_id”,作爲該表的外鍵,同時需要保證該字段的唯一性,否則就不是一對一映射關係了,而是一對多映射關係。表tab_people和tab_idcard(身份證表)之間的關聯關係如下圖所示。

【示例】一對一關聯,獲取公民信息與身份證號碼。

(1)在MySQL數據庫創建tab_people表和tab_idcard表,並添加相關數據。

-- 判斷數據表是否存在,存在則刪除
DROP TABLE IF EXISTS tab_people;
DROP TABLE IF EXISTS tab_idcard;
 
-- 創建“身份證信息”數據表
CREATE TABLE IF NOT EXISTS tab_idcard
( 
	id INT AUTO_INCREMENT PRIMARY KEY COMMENT '身份證ID',
	idCard_code VARCHAR(45) COMMENT '身份證號碼'
) COMMENT = '身份證信息表';
 
-- 創建“公民信息”數據表
CREATE TABLE IF NOT EXISTS tab_people
( 
	id INT AUTO_INCREMENT PRIMARY KEY COMMENT '公民ID',
	NAME VARCHAR(45) NOT NULL COMMENT '公民名稱',
	sex VARCHAR(2) COMMENT '公民性別',
	age INT COMMENT '公民年齡',
	card_id INT UNIQUE COMMENT '身份證ID',
	-- 創建外鍵約束
	FOREIGN KEY fk_card_id (card_id)
	REFERENCES tab_idcard(id)
) COMMENT = '公民信息表';
 
-- 添加數據
INSERT INTO tab_idcard(idCard_code) VALUE('123456789');
INSERT INTO tab_people(NAME,sex,age,card_id) VALUES('pan_junbiao的博客','男',32,1); 

(2)創建名稱爲People.java公民信息的持久化類。

package com.pjb.jpauserdemo.entity;

import javax.persistence.*;

/**
 * 公民信息的持久化類
 * @author pan_junbiao
 **/
@Entity
@Table(name = "tab_people")
public class People
{
    //公民ID
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    //公民名稱
    @Column(name = "name")
    private String name;

    //公民性別
    @Column(name = "sex")
    private String sex;

    //公民年齡
    @Column(name = "age")
    private int age;

    //關聯的身份證對象
    @OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    @JoinColumn(name="card_id")
    private IDcard idcard;

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getSex()
    {
        return sex;
    }

    public void setSex(String sex)
    {
        this.sex = sex;
    }

    public int getAge()
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }

    public IDcard getIdcard()
    {
        return idcard;
    }

    public void setIdcard(IDcard idcard)
    {
        this.idcard = idcard;
    }
}

(3)創建名稱爲IDcard.java身份證信息的持久化類。

package com.pjb.jpauserdemo.entity;

import javax.persistence.*;

/**
 * 身份證信息的持久化類
 * @author pan_junbiao
 **/
@Entity
@Table(name = "tab_idcard")
public class IDcard
{
    //身份證ID
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    //身份證號碼
    @Column(name = "idcard_code")
    private String idCardCode;

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getIdCardCode()
    {
        return idCardCode;
    }

    public void setIdCardCode(String idCardCode)
    {
        this.idCardCode = idCardCode;
    }
}

(4)創建名稱爲PeopleDao.java公民信息數據庫訪問接口,並繼承JpaRepository接口。

package com.pjb.jpauserdemo.dao;

import com.pjb.jpauserdemo.entity.People;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * 公民信息數據庫訪問接口
 * @author pan_junbiao
 **/
@Repository
public interface PeopleDao extends JpaRepository<People,Integer>
{
}

(5) 測試:一對一新增(新增公民信息與關聯的身份證信息)。

@Autowired
private PeopleDao peopleDao;

/**
 * 一對一新增:新增公民信息與關聯的身份證信息
 * @author pan_junbiao
 */
@Test
public void addPeopleAndIdcard()
{
    //創建身份證信息
    IDcard idcard = new IDcard();
    idcard.setIdCardCode("123456789");

    //創建公民信息
    People people = new People();
    people.setName("pan_junbiao的博客");
    people.setSex("男");
    people.setAge(32);
    //將公民與身份證信息關聯
    people.setIdcard(idcard);
    
    //執行新增操作
    peopleDao.save(people);

    //如果新增成功,則可以獲取自增主鍵
    //否則新增失敗,則拋出異常
    if(people.getId()>0)
    {
        System.out.println("新增公民信息成功!");
        System.out.println("公民ID:" + people.getId());
        System.out.println("公民名稱:" + people.getName());
        System.out.println("身份證號碼:" + idcard.getIdCardCode());
    }
}

執行結果:

(6)測試:一對一查詢(獲取公民信息與關聯的身份證信息)。

@Autowired
private PeopleDao peopleDao;

/**
 * 一對一查詢:獲取公民信息與關聯的身份證信息
 * @author pan_junbiao
 */
@Test
public void getPeopleAndIdcard()
{
    People people = peopleDao.findById(1).get();
    if (people != null)
    {
        System.out.println("---------------1、公民信息--------------------");
        System.out.println("公民編號:" + people.getId());
        System.out.println("公民名稱:" + people.getName());
        System.out.println("公民性別:" + people.getSex());
        System.out.println("公民年齡:" + people.getAge());

        //獲取關聯的身份證信息信息
        System.out.println("---------------2、身份證信息信息---------------");
        IDcard idCard = people.getIdcard();
        if(idCard!=null)
        {
            System.out.println("身份證ID:" + idCard.getId());
            System.out.println("身份證號碼:" + idCard.getIdCardCode());
        }
    }
}

執行結果:

 

5、一對多查詢

在日常開發中一對多查詢是常見的,也是業務中十分重要的部分。下面將以生產商對象(類Factory)與產品對象(類Product)爲例,講解JPA的一對多關聯。類Factory與類Product的關聯關係如下圖。

【示例】建立生產商與產品對象對象的一對多關聯,並利用映射關係查詢完整的產品信息。

(1)在MySQL數據庫創建tab_factory表和tab_product表,並添加相關數據。

-- 判斷數據表是否存在,存在則刪除
DROP TABLE IF EXISTS tab_factory;
DROP TABLE IF EXISTS tab_product;

-- 創建“生產商信息”數據表
CREATE TABLE IF NOT EXISTS tab_factory
( 
	factory_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '生產商ID',
	NAME VARCHAR(20) NOT NULL COMMENT '生產商名稱'
) COMMENT = '生產商信息表';
 
-- 創建“產品信息”數據表
CREATE TABLE IF NOT EXISTS tab_product
( 
	id INT AUTO_INCREMENT PRIMARY KEY COMMENT '產品ID',
	NAME VARCHAR(20) NOT NULL COMMENT '產品名稱',
	price DECIMAL(9,2) NOT NULL COMMENT '產品價格',
	factory_id INT COMMENT '生產商ID'
) COMMENT = '產品信息表';
 
-- 添加數據
INSERT INTO tab_factory(NAME) VALUES('華爲公司');
INSERT INTO tab_product(NAME,price,factory_id) VALUES('華爲手機',1299,1);
INSERT INTO tab_product(NAME,price,factory_id) VALUES('華爲路由器',699,1);

(2)創建名稱爲Product.java產品信息的持久化類。

package com.pjb.jpauserdemo.entity;

import javax.persistence.*;
import java.math.BigDecimal;

/**
 * 產品信息的持久化類
 * @author pan_junbiao
 **/
@Entity
@Table(name = "tab_product")
public class Product
{
    //產品ID
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    //產品名稱
    @Column(name = "name")
    private String name;

    //產品價格
    @Column(name = "price")
    private BigDecimal price;

    //生產商信息
    @ManyToOne
    @JoinColumn(name = "factory_id")
    private Factory factory;

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public BigDecimal getPrice()
    {
        return price;
    }

    public void setPrice(BigDecimal price)
    {
        this.price = price;
    }

    public Factory getFactory()
    {
        return factory;
    }

    public void setFactory(Factory factory)
    {
        this.factory = factory;
    }
}

(3)創建名稱爲Factory.java生產商信息的持久化類。

package com.pjb.jpauserdemo.entity;

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

/**
 * 生產商信息的持久化類
 * @author pan_junbiao
 **/
@Entity
@Table(name = "tab_factory")
public class Factory
{
    //生產商ID
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "factory_id")
    private int factoryId;

    //生產商名稱
    @Column(name = "name")
    private String name;

    //產品列表
    @OneToMany
    @JoinColumn(name = "factory_id")
    private List<Product> productList;

    public int getFactoryId()
    {
        return factoryId;
    }

    public void setFactoryId(int factoryId)
    {
        this.factoryId = factoryId;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public List<Product> getProductList()
    {
        return productList;
    }

    public void setProductList(List<Product> productList)
    {
        this.productList = productList;
    }
}

(4)創建名稱爲FactoryDao.java生產商信息數據庫訪問接口,並繼承JpaRepository接口。

package com.pjb.jpauserdemo.dao;

import com.pjb.jpauserdemo.entity.Factory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * 生產商信息數據庫訪問接口
 * @author pan_junbiao
 **/
@Repository
public interface FactoryDao extends JpaRepository<Factory,Integer>
{
}

(5)創建名稱爲ProductDao.java產品信息數據庫訪問接口,並繼承JpaRepository接口。

package com.pjb.jpauserdemo.dao;

import com.pjb.jpauserdemo.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * 產品信息數據庫訪問接口
 * @author pan_junbiao
 **/
@Repository
public interface ProductDao extends JpaRepository<Product,Integer>
{
}

(6)測試:一對多查詢(獲取生產商信息與關聯的產品列表)。

@Autowired
private FactoryDao factoryDao;

/**
 * 一對多查詢:獲取生產商信息與關聯的產品列表
 * @author pan_junbiao
 */
@Test
public void getFactoryAndProductList()
{
    Factory factory = factoryDao.findById(1).get();
    if (factory != null)
    {
        System.out.println("---------------1、生產商信息信息--------------");
        System.out.println("生產商編號:" + factory.getFactoryId());
        System.out.println("生產商名稱:" + factory.getName());

        //獲取關聯的產品信息信息
        System.out.println("---------------2、產品信息信息---------------");
        List<Product> productList = factory.getProductList();
        if(productList!=null && productList.size()>0)
        {
            for(Product product : productList)
            {
                System.out.println("產品編號:" + product.getId());
                System.out.println("產品名稱:" + product.getName());
                System.out.println("產品價格:" + product.getPrice());
                System.out.println("-------------------");
            }
        }
    }
}

執行結果:

(7)測試:多對一查詢(獲取產品信息與關聯的生產商信息)。

@Autowired
private ProductDao productDao;

/**
 * 多對一查詢:獲取產品信息與關聯的生產商信息
 * @author pan_junbiao
 */
@Test
public void getProductAndFactory()
{
    List<Product> productList = productDao.findAll();
    if(productList!=null && productList.size()>0)
    {
        for (Product product : productList)
        {
            //獲取產品信息
            System.out.println("產品編號:" + product.getId());
            System.out.println("產品名稱:" + product.getName());
            System.out.println("產品價格:" + product.getPrice());

            //獲取關聯的生產商信息信息
            Factory factory = product.getFactory();
            System.out.println("生產商編號:" + factory.getFactoryId());
            System.out.println("生產商名稱:" + factory.getName());
            System.out.println("-------------------");
        }
    }
}

執行結果:

 

6、多對多查詢

多對多關聯關係是比較特殊的一種關聯關係,它與一對一和一對多關聯關係不同,需要通過另外的一張表保存多對多的映射關係。下面將以應用系統中的權限分配爲例講解多對多的關聯關係,例如用戶可以擁有多個系統的操作權限,而一個權限又可以被賦予多個用戶,這就是典型的多對多關聯映射關係。其中用戶表(tab_user)和權限表(tab_user)的表關係如下圖所示。

說明:由於多對多關聯關係的查詢對第3個表進行反覆查詢,在一定程度上會影響系統的性能效率,所以在應用中儘量少使用多對多關聯關係的表結果。

【示例】建立用戶對象與權限對象的多對多關聯關係,查詢用戶admin所擁有的權限,以及權限“新聞管理”被賦予了哪些用戶。

(1)在MySQL數據庫創建用戶表(tab_user)、權限表(tab_role)和映射表(tab_mapping),並添加相關數據。

-- 判斷數據表是否存在,存在則刪除
DROP TABLE IF EXISTS tab_user;
DROP TABLE IF EXISTS tab_role;
DROP TABLE IF EXISTS tab_mapping;
 
-- 創建“用戶信息”數據表
CREATE TABLE IF NOT EXISTS tab_user
( 
	id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用戶ID',
	NAME VARCHAR(45) NOT NULL COMMENT '用戶名稱'
) COMMENT = '用戶信息表';
 
-- 創建“權限信息”數據表
CREATE TABLE IF NOT EXISTS tab_role
( 
	id INT AUTO_INCREMENT PRIMARY KEY COMMENT '權限ID',
	role_name VARCHAR(45) NOT NULL COMMENT '權限名稱'
) COMMENT = '權限信息表';
 
-- 創建“映射信息”數據表
CREATE TABLE IF NOT EXISTS tab_mapping
( 
	id INT AUTO_INCREMENT PRIMARY KEY COMMENT '映射ID',
	user_id INT COMMENT '用戶Id',
	role_id INT COMMENT '權限Id'
) COMMENT = '映射信息表';
 
-- 添加數據
INSERT INTO tab_user(NAME) VALUES('admin'),('pan_junbiao的博客');
INSERT INTO tab_role(role_name) VALUES('系統管理員'),('新聞管理員'),('廣告管理員');
INSERT INTO tab_mapping(user_id,role_id) VALUES(1,1),(1,2),(1,3),(2,2),(2,3);

(2)創建名稱爲User.java用戶信息的持久化類。

package com.pjb.jpauserdemo.entity;

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

/**
 * 用戶信息的持久化類
 * @author pan_junbiao
 **/
@Entity
@Table(name = "tab_user")
public class User
{
    //用戶ID
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    //用戶名稱
    @Column(name = "name")
    private String name;

    //引用的權限實體對象集合
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "tab_mapping",joinColumns = {@JoinColumn(name = "user_id")},inverseJoinColumns = {@JoinColumn(name="role_id")})
    private List<Role> roleList;

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public List<Role> getRoleList()
    {
        return roleList;
    }

    public void setRoleList(List<Role> roleList)
    {
        this.roleList = roleList;
    }
}

(3)創建名稱爲Role.java權限信息的持久化類。

package com.pjb.jpauserdemo.entity;

import javax.persistence.*;

/**
 * 權限信息的持久化類
 * @author pan_junbiao
 **/
@Entity
@Table(name = "tab_role")
public class Role
{
    //權限ID
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    //權限名稱
    @Column(name = "role_name")
    private String roleName;

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getRoleName()
    {
        return roleName;
    }

    public void setRoleName(String roleName)
    {
        this.roleName = roleName;
    }
}

(4)創建名稱爲UserDao.java用戶信息數據庫訪問接口,並繼承JpaRepository接口。

package com.pjb.jpauserdemo.dao;

import com.pjb.jpauserdemo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * 用戶信息數據庫訪問接口
 * @author pan_junbiao
 **/
@Repository
public interface UserDao extends JpaRepository<User,Integer>
{
}

(5)測試:多對多查詢(獲取用戶信息與關聯的權限列表)。

@Autowired
private UserDao userDao;

/**
 * 多對多查詢:獲取用戶信息與關聯的權限列表
 * @author pan_junbiao
 */
@Test
public void getUserAndRole()
{
    List<User> userList = userDao.findAll();
    if(userList!=null && userList.size()>0)
    {
        //遍歷用戶列表
        for(User user : userList)
        {
            System.out.println("用戶編號:" + user.getId());
            System.out.println("用戶名稱:" + user.getName());

            //獲取權限列表
            List<Role> roleList = user.getRoleList();
            if(roleList!=null && roleList.size()>0)
            {
                System.out.print("用戶擁有的權限:");
                for (Role role : roleList)
                {
                    System.out.print(role.getRoleName()+";");
                }
            }
            System.out.println("\n-----------------------------------------------");
        }
    }
}

執行結果:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章