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&
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-----------------------------------------------");
}
}
}
執行結果: