写点什么

Spring 全家桶之 Spring Data JPA(五)

作者:小白
  • 2022 年 8 月 21 日
    上海
  • 本文字数:9767 字

    阅读完需:约 32 分钟

Spring 全家桶之 Spring Data JPA(五)

## 一、多表操作之多对多

### 创建 many2many 项目

创建 maven 项目,并添加依赖

```xml

<properties>

<spring.version>5.0.2.RELEASE</spring.version>

<hibernate.version>5.0.7.Final</hibernate.version>

<slf4j.version>1.6.6</slf4j.version>

<log4j.version>1.2.12</log4j.version>

<c3p0.version>0.9.1.2</c3p0.version>

<mysql.version>8.0.19</mysql.version>

</properties>

<dependencies>

<!-- junit 单元测试 -->

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.12</version>

<scope>test</scope>

</dependency>

<!-- spring beg -->

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjweaver</artifactId>

<version>1.6.8</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aop</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context-support</artifactId>

<version>${spring.version}</version>

</dependency>

<!-- spring 对 orm 框架的支持包-->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-orm</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-beans</artifactId>

<version>${spring.version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-core</artifactId>

<version>${spring.version}</version>

</dependency>

<!-- spring end -->

<!-- hibernate beg -->

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-core</artifactId>

<version>${hibernate.version}</version>

</dependency>

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-entitymanager</artifactId>

<version>${hibernate.version}</version>

</dependency>

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-validator</artifactId>

<version>5.2.1.Final</version>

</dependency>

<!-- hibernate end -->

<!-- c3p0 beg -->

<dependency>

<groupId>c3p0</groupId>

<artifactId>c3p0</artifactId>

<version>${c3p0.version}</version>

</dependency>

<!-- c3p0 end -->

<!-- log end -->

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>${log4j.version}</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-api</artifactId>

<version>${slf4j.version}</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-log4j12</artifactId>

<version>${slf4j.version}</version>

</dependency>

<!-- log end -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>${mysql.version}</version>

</dependency>

<!-- spring data jpa 的坐标-->

<dependency>

<groupId>org.springframework.data</groupId>

<artifactId>spring-data-jpa</artifactId>

<version>1.9.0.RELEASE</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>${spring.version}</version>

</dependency>

<!-- el beg 使用 spring data jpa 必须引入 -->

<dependency>

<groupId>javax.el</groupId>

<artifactId>javax.el-api</artifactId>

<version>2.2.4</version>

</dependency>

<dependency>

<groupId>org.glassfish.web</groupId>

<artifactId>javax.el</artifactId>

<version>2.2.4</version>

</dependency>

<!-- el end -->

</dependencies>

```

在 resource 目录下新增 applicationContext.xml,新增以下内容

```xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"

xsi:schemaLocation="

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd

http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd

http://www.springframework.org/schema/data/jpa

http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

<!--spring 和 spring data jpa 的配置-->

<!-- 配置包扫描-->

<context:component-scan base-package="com.citi" ></context:component-scan>

<!-- 1.创建 entityManagerFactory 对象交给 spring 容器管理-->

<bean id="entityManagerFactoty" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">

<property name="dataSource" ref="dataSource" />

<!--配置的扫描的包(实体类所在的包) -->

<property name="packagesToScan" value="com.citi.entity" />

<!-- jpa 的实现厂家 -->

<property name="persistenceProvider">

<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>

</property>

<!--jpa 的供应商适配器 -->

<property name="jpaVendorAdapter">

<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">

<!--配置是否自动创建数据库表 -->

<property name="generateDdl" value="false" />

<!--指定数据库类型 -->

<property name="database" value="MYSQL" />

<!--数据库方言:支持的特有语法 -->

<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />

<!--是否显示 sql -->

<property name="showSql" value="true" />

</bean>

</property>

<!--jpa 的方言 :高级的特性 -->

<property name="jpaDialect" >

<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />

</property>

<!--

注入 jpa 的配置信息

记载 jpa 的基本配置信息和 jpa 实现方式的配置信息-->

<property name="jpaProperties">

<props>

<prop key="hibernate.hbm2ddl.auto">create</prop>

</props>

</property>

</bean>

<!--2.创建数据库连接池 -->

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

<property name="user" value="root"></property>

<property name="password" value="Abc*123456"></property>

<property name="jdbcUrl" value="jdbc:mysql://rm-uf67r962043910k193o.mysql.rds.aliyuncs.com:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai" ></property>

<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>

</bean>

<!--3.整合 spring dataJpa-->

<jpa:repositories base-package="com.citi.dao" transaction-manager-ref="transactionManager"

entity-manager-factory-ref="entityManagerFactoty"></jpa:repositories>

<!--4.配置事务管理器 -->

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">

<property name="entityManagerFactory" ref="entityManagerFactoty"></property>

</bean>

<!-- 4.txAdvice-->

<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<tx:method name="save*" propagation="REQUIRED"/>

<tx:method name="insert*" propagation="REQUIRED"/>

<tx:method name="update*" propagation="REQUIRED"/>

<tx:method name="delete*" propagation="REQUIRED"/>

<tx:method name="get*" read-only="true"/>

<tx:method name="find*" read-only="true"/>

<tx:method name="*" propagation="REQUIRED"/>

</tx:attributes>

</tx:advice>

</beans>

```

新增 entity 包,编写两个实体类 User,Role,两者为多对多关系,一个用户可以有多个角色,一个角色也包含了多个用户

```java

public class User {

private Long userId;

private String userName;

private Integer age;

// 此处省略 getter/setter/toString 方法

}

```

```java

public class Role {

private Long roleId;

private String roleName;

// 此处省略 getter/setter/toString 方法

```

新增 dao 包,编写 UserDao 和 RoleDao,并继承 JpaRepository 和 JpaSpecificationExecutor

```java

public interface UserDao extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {

}

```

```java

public interface RoleDao extends JpaRepository<Role,Long>, JpaSpecificationExecutor<Role> {

}

```

在 test 包中新建测试类 Many2ManyTest

```java

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = "classpath:applicationContext.xml")

public class Many2ManyTest {

@Autowired

private UserDao userDao;

@Autowired

private RoleDao roleDao;

}

```

### 配置多对多映射关系

- 添加 @Entity 注解,表示该类是一个实体类

- 增加 @Table 注解,表明该实体类对应的表名称

- 增加 @Id 及 @Column,建立实体类属性和数据库字段之间的映射关系

- 新增角色属性,并添加 getter/setter 方法,用户的角色是一组集合,用 Set 表示

- 在角色集合上增加 @ManyToMany 注解,表明多对多的关系

- @JoinTable 表示配置中间表,name 表示中间表的名称,joinColumns 配置的是当前对象在中间表中的外键,name 值得值中间表的主键,referencedColumnName 当前类对应表的主键,inverseJoinColumns:对方对象在中间表的外键

```java

@Entity

@Table(name = "sys_user")

public class User {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

@Column(name = "user_id")

private Long userId;

@Column(name = "user_name")

private String userName;

@Column(name = "age")

private Integer age;

/**

* 配置多对多的映射关系

* @ManyToMany:声明表的映射关系为多对多关系,targetEntity 为对方实体类的字节码

* @JoinTable:配置中间表,name 为中间表的名称,

* joinColumns 配置的是当前对象在中间表中的外键,name 值得值中间表的主键,referencedColumnName 当前类对应表的主键

* inverseJoinColumns:对方对象在中间表的外键

*/

@ManyToMany(targetEntity = Role.class)

@JoinTable(name = "sys_user_role",

joinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")},

inverseJoinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}

)

private Set<Role> roleSet = new HashSet<>();

public Set<Role> getRoleSet() {

return roleSet;

}

public void setRoleSet(Set<Role> roleSet) {

this.roleSet = roleSet;

}

```

```java

@Entity

@Table(name = "sys_role")

public class Role {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

@Column(name = "role_id")

private Long roleId;

@Column(name = "role_name")

private String roleName;

@ManyToMany(targetEntity = User.class)

@JoinTable(name = "sys_user_role",

joinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")},

inverseJoinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")}

)

private Set<User> userSet = new HashSet<>();

public Set<User> getUserSet() {

return userSet;

}

public void setUserSet(Set<User> userSet) {

this.userSet = userSet;

}

```

### 多对多表操作

在 Many2ManyTest 中新增方法 testSave()

```java

@Test

@Transactional

@Rollback(false)

public void testSave(){

User user = new User();

user.setUserName("Thor");

Role role = new Role();

role.setRoleName("God");

userDao.save(user);

roleDao.save(role);

}

```

查看执行的 SQL 语句,执行了 3 条 create 语句,2 条 insert 语句

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3b56141904544e20bf775da6a01d6942~tplv-k3u1fbpfcp-watermark.image?)

查看数据库表,中间表没有插入数据,user 和 role 关联关系没有建立成功

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d8b27c3491854dad890ed37b22f24729~tplv-k3u1fbpfcp-watermark.image?)

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da5a8184f7bf44ce8774bc00d5cdc184~tplv-k3u1fbpfcp-watermark.image?)

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9ea7c91f6eb1435c8f612f9913224816~tplv-k3u1fbpfcp-watermark.image?)

新增 testSave0()方法,在 user 一侧建立用户到角色的关联关系

```java

@Test

@Transactional

@Rollback(false)

public void testSave0(){

User user = new User();

user.setUserName("Thor");

Role role = new Role();

role.setRoleName("God");

user.getRoleSet().add(role); //配置用户到角色的映射

userDao.save(user);

roleDao.save(role);

}

```

后台执行 SQL 如下,3 条 create 语句,3 条 insert 语句,中间表也被插入数据

![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c383d826ee5440b5b9a253b87c8eadc0~tplv-k3u1fbpfcp-watermark.image?)

查看数据库表,3 张表中都有数据,user 和 role 关联关系建立

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/44383f4ae7f647e6a03ba8ed5aeacdc4~tplv-k3u1fbpfcp-watermark.image?)

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ea29c84a544d4efcb8958b9be6ab02ba~tplv-k3u1fbpfcp-watermark.image?)

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/70876351042647feac27070424f46797~tplv-k3u1fbpfcp-watermark.image?)

同时在 user 和 role 两侧建立关联关系

```java

@Test

@Transactional

@Rollback(false)

public void testSave1(){

User user = new User();

user.setUserName("Thor");

Role role = new Role();

role.setRoleName("God");

// 两放都配置会产生主键冲突,只需要一方配置即可

// 多对多放弃维护权,被动的一方放弃

user.getRoleSet().add(role); //配置用户到角色的映射

role.getUserSet().add(user); //配置角色到用户的映射

userDao.save(user);

roleDao.save(role);

}

```

后台执行 SQL 如下,摒弃饿 SQL 执行出现报错,因为 role 在执行往中间表执行 insert 操作时表中已经存在了 user 插入的数据,所以出现了主键冲突的报错

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b78698d38a9b4880a7ac4ea9a9f24db8~tplv-k3u1fbpfcp-watermark.image?)

因此需要 user 和 role 一方放弃维护权,修改 Role 实体类中关联关系,mappedBy 是指 role 在对方表的属性名称

```java

//@ManyToMany(targetEntity = User.class)

//@JoinTable(name = "sys_user_role",

// joinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")},

// inverseJoinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")}

//)

// 放弃维护权

@ManyToMany(mappedBy = "roleSet")

private Set<User> userSet = new HashSet<>();

```

级联添加操作,修改 applicationContext.xml 中的配置,从 create 改为 update,这样每次执行时不会删除表在建立,而是直接更新

```xml

<!--

注入 jpa 的配置信息

记载 jpa 的基本配置信息和 jpa 实现方式的配置信息-->

<property name="jpaProperties">

<props>

<prop key="hibernate.hbm2ddl.auto">update</prop>

</props>

</property>

```

User 类添加级联操作属性

```java

@ManyToMany(targetEntity = Role.class, cascade = CascadeType.ALL)

@JoinTable(name = "sys_user_role",

joinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")},

inverseJoinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}

)

private Set<Role> roleSet = new HashSet<>();

```

在 Many2ManyTest 中增加方法,执行级联添加操作

```java

// 测试级联添加

@Test

@Transactional

@Rollback(false)

public void testCascadeSave(){

// 操作主题为 user

User user = new User();

user.setUserName("Peter");

Role role = new Role();

role.setRoleName("Human");

user.getRoleSet().add(role); //配置用户到角色的映射

userDao.save(user);

}

```

后台执行 SQL 如下,3 张表中都执行了 insert 操作

![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8e4bfc3012114cac81cc11841343e3a8~tplv-k3u1fbpfcp-watermark.image?)

数据库表中成功插入数据

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ff24e66825b491b879b7f22f519a201~tplv-k3u1fbpfcp-watermark.image?)

![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c67c4141921845e59d5d3041ecb07f57~tplv-k3u1fbpfcp-watermark.image?)

![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c93ae0ddfecf48b8862df509ad86c2e4~tplv-k3u1fbpfcp-watermark.image?)

测试级联删除

```java

@Test

@Transactional

@Rollback(false)

public void testCascadeDelete(){

User one = userDao.findOne(2L);

userDao.delete(one);

}

```

执行的 SQL 如下图

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8f57a864c6fe4e829441eadc9bd6acf0~tplv-k3u1fbpfcp-watermark.image?)

查看数据库表,三张表中关联数据已被删除

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/86477fb7fdcc4ce98a6c538d20b2f9a3~tplv-k3u1fbpfcp-watermark.image?)

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c259481aae424f2d82f4de00b4bcb429~tplv-k3u1fbpfcp-watermark.image?)

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/219f5e324c934aa0aa9e7f3bfe998743~tplv-k3u1fbpfcp-watermark.image?)

### 多表查询

对象导航查询:查询一个对象的同时,通过此对象查询他的关联对象

使用 Chapter 04 中的 one2many 项目,在 test 包中新建 ObjectQueryTest 测试类

```java

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = "classpath:applicationContext.xml")

public class ObjectQueryTest {

@Autowired

private CustomerDao customerDao;

@Autowired

private LinkManDao linkManDao;

```

//测试对象导航查询,查询一个对象的时候,通过此对象查询所有的关联对象

@Test

public void testQuery1(){

Customer customer = customerDao.getOne(2L);

Set<LinkMan> linkManSet = customer.getLinkManSet();

for (LinkMan linkMan : linkManSet) {

System.out.println(linkMan);

}

}

}

```

执行 testQuery1()方法,控制台报错,需要在方法上添加 @Transactional

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a874fdaed46847da9995adf4f5e9b555~tplv-k3u1fbpfcp-watermark.image?)

再次执行该方法,控制台显示查询成功

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6d940811f7cb40d6b90cd6d4dacb34d6~tplv-k3u1fbpfcp-watermark.image?)

新增测试方法 testQuery2(),使用 findOne()执行查询

```java

@Test

@Transactional

public void testQuery2(){

Customer customer = customerDao.findOne(2L);

Set<LinkMan> linkManSet = customer.getLinkManSet();

for (LinkMan linkMan : linkManSet) {

System.out.println(linkMan);

}

}

```

查询结果

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e346affed5342e6b5c1b429893811fe~tplv-k3u1fbpfcp-watermark.image?)

对象导航查询默认使用延迟加载的形式查询,调用 getOne 方法不会立即发送查询,而是在使用关联对象的时候才会执行,如果将延迟加载改为立即加载,需要修改配置

fetch 配置关联对象的加载方式

- FetchType.LAZY:延迟加载

- FetchType.EAGER:立即加载

修改 Customer 实体类,增加 fetch 配置

```java

@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)

private Set<LinkMan> linkManSet = new HashSet<>();

```

在 ObjectQueryTest 类中增加 testQuery3(),从 LinkMan 查询 Customer

```java

@Test

@Transactional

public void testQuery3(){

LinkMan linkMan = linkManDao.getOne(2L);

Customer customer = linkMan.getCustomer();

System.out.println(customer);

}

```

控制台输出结果如下

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4410e746200f4e5f92ba339ec929db80~tplv-k3u1fbpfcp-watermark.image?)

从一方查询多方,查询结果为集合或者列表,默认使用延迟加载

从多方查询一方,默认使用立即加载

Spring Data JPA 完结 🎉🎉🎉

用户头像

小白

关注

QA 2019.08.05 加入

微信号JingnanSJ或者公众号RiemannHypo获取异步和图灵系列书籍

评论

发布
暂无评论
Spring 全家桶之 Spring Data JPA(五)_8月月更_小白_InfoQ写作社区