目录
  1. 第1章 Spring Data JPA的概述
    1. 1.1 Spring Data JPA概述
    2. 1.2 Spring Data JPA的特性
    3. 1.3 Spring Data JPA 与 JPA和hibernate之间的关系
  2. 第2章 Spring Data JPA的快速入门
    1. 2.1 需求说明
    2. 2.2 搭建Spring Data JPA的开发环境
      1. 2.2.1 引入Spring Data JPA的坐标
      2. 2.2.2 整合Spring Data JPA与Spring
      3. 2.2.3 使用JPA注解配置映射关系
    3. 2.3 使用Spring Data JPA完成需求
      1. 2.3.1 编写符合Spring Data JPA规范的Dao层接口
      2. 2.3.2 完成基本CRUD操作
  3. 第3章 Spring Data JPA的内部原理剖析
    1. 3.1 Spring Data JPA的常用接口分析
    2. 3.2 Spring Data JPA的实现过程
    3. 3.3 Spring Data JPA完整的调用过程分析
  4. 第4章 Spring Data JPA的查询方式
    1. 4.1 使用Spring Data JPA中接口定义的方法进行查询
    2. 4.2 使用JPQL的方式查询
    3. 4.3 使用SQL语句查询
    4. 4.4 方法命名规则查询
  5. 第5章 Specifications动态查询
    1. 5.1 使用Specifications完成条件查询
    2. 5.2 基于Specifications的分页查询
    3. 5.3 方法对应关系
  6. 第6章 多表设计
    1. 6.1 表之间关系的划分
    2. 6.2 在JPA框架中表关系的分析步骤
      1. 第一步:首先确定两张表之间的关系。
      2. 第二步:在数据库中实现两张表的关系
      3. 第三步:在实体类中描述出两个实体的关系
      4. 第四步:配置出实体类和数据库表的关系映射(重点)
  7. 第7章 JPA中的一对多
    1. 7.1 示例分析
    2. 7.2 表关系建立
    3. 7.3 实体类关系建立以及映射配置
    4. 7.4 映射的注解说明
    5. 7.5 一对多的操作
      1. 7.5.1 添加
    6. 7.5.2 删除
    7. 7.5.3 级联操作
  8. 第8章 JPA中的多对多
    1. 8.1 示例分析
    2. 8.2 表关系建立
    3. 8.3 实体类关系建立以及映射配置
SpringDataJpa快速入门

第1章 Spring Data JPA的概述

1.1 Spring Data JPA概述

Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦

1.2 Spring Data JPA的特性


SpringData Jpa 极大简化了数据库访问层代码。 如何简化的呢? 使用了SpringDataJpa,我们的dao层中只需要写接口,就自动具有了增删改查、分页查询等方法。

1.3 Spring Data JPA 与 JPA和hibernate之间的关系

JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)

Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。

第2章 Spring Data JPA的快速入门

2.1 需求说明

Spring Data JPA完成客户的基本CRUD操作

2.2 搭建Spring Data JPA的开发环境

2.2.1 引入Spring Data JPA的坐标

使用Spring Data JPA,需要整合Spring与Spring Data JPA,并且需要提供JPA的服务提供者hibernate,所以需要导入spring相关坐标,hibernate坐标,数据库驱动坐标等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<properties>
<spring.version>4.2.4.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>5.1.6</mysql.version>
</properties>

<dependencies>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</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>

<!--springdatajpa的坐标-->
<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>

2.2.2 整合Spring Data JPA与Spring

在resources下创建applicationContext.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?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 和springDataJpa配置-->
<!--1.创建entityManagerFactory对象交给Spring管理-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--配置的是扫描的包(实体类的所在的包)-->
<property name="packagesToScan" value="com.byliuyu.domain"/>
<!--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"/>
<!--数据库方言,支持的特有语法。比如oracle的rownum,mysql的limit-->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
<!--是否显示sql语句-->
<property name="showSql" value="true"/>
</bean>
</property>
<!--jpa的方言,高级特性,不是必须写,比如hibernate的一级缓存 二级缓存-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
</bean>

<!--2.创建数据库连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"/>
<property name="password" value="1234"/>
<property name="jdbcUrl" value="jdbc:mysql:///jpa"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>



<!--3.整合SpringDataJpa,dao的目录-->
<jpa:repositories base-package="com.byliuyu.dao" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"/>


<!--4.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<!--5.声明式事务-->

<!--6.配置包扫描,扫描此包下的所有注解-->
<context:component-scan base-package="com.byliuyu"/>
</beans>

2.2.3 使用JPA注解配置映射关系

创建Customer对象,并配置好映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/***
* 1.实体类和表的映射关系
* @Entity
* @Table
* 2.类中属性和表字段的映射关系
* @Id
* @GeneratedValue
* @Column
*/
@Entity
@Table(name = "cst_customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Long custId; //客户的主键

@Column(name = "cust_name")
private String custName;//客户名称

@Column(name="cust_source")
private String custSource;//客户来源

@Column(name="cust_level")
private String custLevel;//客户级别

@Column(name="cust_industry")
private String custIndustry;//客户所属行业

@Column(name="cust_phone")
private String custPhone;//客户的联系方式

@Column(name="cust_address")
private String custAddress;//客户地址
//此处省略getter和setter方法 .. ..
}

2.3 使用Spring Data JPA完成需求

2.3.1 编写符合Spring Data JPA规范的Dao层接口

Spring Data JPA是spring提供的一款对于数据访问层(Dao层)的框架,使用Spring Data JPA,只需要按照框架的规范提供dao接口,不需要实现类就可以完成数据库的增删改查、分页查询等方法的定义,极大的简化了我们的开发过程。
在Spring Data JPA中,对于定义符合规范的Dao层接口,我们只需要遵循以下几点就可以了:
1.创建一个Dao层接口,并实现JpaRepository和JpaSpecificationExecutor
2.提供相应的泛型

1
2
3
4
5
6
7
8
9
10
/**
* 符合springDataJpa的接口规范,必须继承paRepository和JpaSpecificationExecutor接口
* JpaRepository<操作的实体类型,实体类主键属性的类型>
* *封装了基本的crud操作
* JpaSpecificationExecutor<操作的实体类型>
* *封装了复杂查询,比如分页操作
*/
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
//do coder...
}

这样我们就定义好了一个符合Spring Data JPA规范的Dao层接口

2.3.2 完成基本CRUD操作

完成了Spring Data JPA的环境搭建,并且编写了符合Spring Data JPA 规范的Dao层接口之后,就可以使用定义好的Dao层接口进行客户的基本CRUD操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@RunWith(SpringJUnit4ClassRunner.class)  //声明Spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml") //指定Spring容器的配置信息
public class CustomerDaoTest {
@Autowired
private CustomerDao customerDao;


/**
* 根据id查询
*/
@Test
public void testFindOne(){
Customer customer = customerDao.findOne(2L);
System.out.println(customer);
}


/**
* save方法:保存或者更新
* 根据传递的对象是否存在主键id,如果没有主键id属性:保存
* 存在id属性,根据id查询数据,更新数据
*/
@Test
public void testSave(){
Customer customer=new Customer();
customer.setCustName("LIUYU BLOG");
customer.setCustLevel("VIP");
customer.setCustIndustry("JavaDEVELOP");
customerDao.save(customer);
}


@Test
public void testUpdate(){
Customer customer=new Customer();
customer.setCustIndustry("C++");
customer.setCustId(4L);
customerDao.save(customer);
}


/**
* 根据id删除操作
*
*/

@Test
public void testDelete(){
customerDao.delete(1L);
}


/**
* 统计查询,查询客户总数量
* count:统计总条数
*/
@Test
public void testCount(){
long count = customerDao.count();
System.out.println(count);
}


/**
* 判断id为1的客户是否存在
* 1.可以查询id为1的客户,如果为空,代表不存在,如果不为空,代表存在
* 2.判断数据库中id为1的客户数量,如果数量为0,代表不存在,如果大于0,代表存在
*/
@Test
public void testExists(){
boolean exists = customerDao.exists(2L);
System.out.println(exists);
}


/**
* 根据id从数据库中查询
* @Transactional: 保证getOne能正常运行
*
* findone: em.find() 立即加载
*
* getOne: em.getReference 延迟加载
* 返回的是一个客户动态代理对象,什么时候用,什么时候加载
*/
@Test
@Transactional
public void testGetOne(){
Customer customer = customerDao.getOne(2L);
System.out.println(customer);
}
}

第3章 Spring Data JPA的内部原理剖析

3.1 Spring Data JPA的常用接口分析

在客户的案例中,我们发现在自定义的CustomerDao中,并没有提供任何方法就可以使用其中的很多方法,那么这些方法究竟是怎么来的呢?答案很简单,对于我们自定义的Dao接口,由于继承了JpaRepository和JpaSpecificationExecutor,所以我们可以使用这两个接口的所有方法。

在使用Spring Data JPA时,一般实现JpaRepository和JpaSpecificationExecutor接口,这样就可以使用这些接口中定义的方法,但是这些方法都只是一些声明,没有具体的实现方式,那么在 Spring Data JPA中它又是怎么实现的呢?

3.2 Spring Data JPA的实现过程

通过对客户案例,以debug断点调试的方式,通过分析Spring Data JPA的原来来分析程序的执行过程
我们以findOne方法为例进行分析

  • 代理子类的实现过程

    断点执行到方法上时,我们可以发现注入的customerDao对象,本质上是通过JdkDynamicAopProxy生成的一个代理对象

  • 代理对象中方法调用的分析
    当程序执行的时候,会通过JdkDynamicAopProxy的invoke方法,对customerDao对象生成动态代理对象。根据对Spring Data JPA介绍而知,要想进行findOne查询方法,最终还是会出现JPA规范的API完成操作,那么这些底层代码存在于何处呢?答案很简单,都隐藏在通过JdkDynamicAopProxy生成的动态代理对象当中,而这个动态代理对象就是SimpleJpaRepository

    通过SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?

带着问题继续查找em对象,我们发现em就是EntityManager对象,而他是JPA原生的实现方式,所以我们得到结论Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发

3.3 Spring Data JPA完整的调用过程分析

第4章 Spring Data JPA的查询方式

4.1 使用Spring Data JPA中接口定义的方法进行查询

在继承JpaRepository,和JpaRepository接口后,我们就可以使用接口中定义的方法进行查询

  • 继承JpaRepository后的方法列表

  • 继承JpaSpecificationExecutor的方法列表

4.2 使用JPQL的方式查询

使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询
@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 案例:根据客户名称查询客户
* jpql:from customer where custName = ?
* 配置jpql语句:使用@Query注解
*/
@Query(value = "from Customer where custName = ?")
public Customer findJpql(String custName);

/**
* 案例:根据客户名称和客户id查询客户
* jpql:from customer where custName = ? and custId =?
*
* 对于多个占位符参数,赋值的时候,默认情况下,占位符的位置需要和方法参数中的位置保持一致
*
* 可以指定占位符的参数位置
* ?索引的方式,指定占位符的取值来源
*
*/
@Query(value = "from Customer where custName = ?2 and custId =?1")
/* public Customer findCustNameAndId(String name,Long id);*/
public Customer findCustNameAndId(Long id,String name);

此外,也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 使jpql完成更新操作
* 案例:根据id更新客户名称
*
* sql:update cst_customer set cust_name =? where cust_id= ?
* jpql: update Customer set custName= ? and custId = ?
*
* @Query: 代表查询操作
* @Modifying: 代表更新操作
*
*/
@Query(value = "update Customer set custName= ?2 where custId = ?1")
@Modifying
public void updateCustomer(Long id,String name);

4.3 使用SQL语句查询

Spring Data JPA同样也支持sql语句的查询,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 使用sql的形式查询:
* 查询全部的客户
* sql : select * from cst_customer;
* Query : 配置sql查询
* value : sql语句
* nativeQuery : 查询方式
* true : sql查询
* false:jpql查询
*
*/
//@Query(value = " select * from cst_customer" ,nativeQuery = true)
@Query(value="select * from cst_customer where cust_name like ?1",nativeQuery = true)
public List<Object [] > findSql(String name);

4.4 方法命名规则查询

顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。
框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30


/**
* 方法名的约定:
* findBy : 查询
* 对象中的属性名(首字母大写) : 查询的条件
* CustName
* * 默认情况 : 使用 等于的方式查询
* 特殊的查询方式
*
* findByCustName -- 根据客户名称查询
*
* 再springdataJpa的运行阶段
* 会根据方法名称进行解析 findBy from xxx(实体类)
* 属性名称 where custName =
*
* 1.findBy + 属性名称 (根据属性名称进行完成匹配的查询=)
* 2.findBy + 属性名称 + “查询方式(Like | isnull)”
* findByCustNameLike
* 3.多条件查询
* findBy + 属性名 + “查询方式” + “多条件的连接符(and|or)” + 属性名 + “查询方式”
*/
public Customer findByCustName(String custName);


//根据姓名模糊查询
public List<Customer> findByCustNameLike(String custName);

//使用客户名称模糊匹配和客户所属行业精准匹配的查询
public Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);

具体的关键字,使用方法和生产成SQL如下表所示:

Keyword Sample JPQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Equals findByFirstnameIs/findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
TRUE findByActiveTrue() … where x.active = true
FALSE findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

第5章 Specifications动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.List;

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;

/**
* JpaSpecificationExecutor中定义的方法
**/
public interface JpaSpecificationExecutor<T> {
//根据条件查询一个对象
T findOne(Specification<T> spec);
//根据条件查询集合
List<T> findAll(Specification<T> spec);
//根据条件分页查询
Page<T> findAll(Specification<T> spec, Pageable pageable);
//排序查询查询
List<T> findAll(Specification<T> spec, Sort sort);
//统计查询
long count(Specification<T> spec);
}

对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。

Specification接口中只定义了如下一个方法:

1
2
3
4
5
6
7
//构造查询条件
/**
* root :Root接口,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象,用来自定义查询
* cb :用来构建查询,此对象里有很多条件方法
**/
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

5.1 使用Specifications完成条件查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
@Autowired
private CustomerDao customerDao;

/**
* 根据条件,查询单个对象
*
*/
@Test
public void testSpec(){
//匿名内部类
/**
* 自定义查询条件
* 1.实现Specification接口(提供泛型:查询的对象类型)
* 2.实现toPredicate方法(构造查询条件)
* 3.需要借助方法参数中的两个参数(
* root:获取需要查询的对象属性
* CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)
* )
* 案例:根据客户名称查询,查询客户名为传智播客的客户
* 查询条件
* 1.查询方式
* cb对象
* 2.比较的属性名称
* root对象
*
*/
Specification<Customer> spec=new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//1.获取比较的属性
Path<Object> custName = root.get("custName");
//2.构造查询条件 : select * from cst_customer where cust_name = '传智播客'
/**
* 第一个参数:需要比较的属性(path对象)
* 第二个参数:当前需要比较的取值
*/
Predicate predicate = cb.equal(custName, "Spring");//进行精准的匹配 (比较的属性,比较的属性的取值)
return predicate;
}
};
Customer customer=customerDao.findOne(spec);
System.out.println(customer);
}

/**
* 多条件查询
* 案例:根据客户名(Spring)和客户所属行业查询(java)
*/
@Test
public void testQueryBynameAndcustIndustry(){
/**
* root:获取属性
* 客户名
* 所属行业
* cb:构造查询
* 1.构造客户名的精准匹配查询
* 2.构造所属行业的精准匹配查询
* 3.将以上两个查询联系起来
*/
Specification<Customer> spec=new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//获取客户名属性
Path<Object>custName=root.get("custName");
//获取所属行业属性
Path<Object>custIndustry=root.get("custIndustry");

//构造查询
//1.构造客户名的精准匹配查询
Predicate p1=cb.equal(custName,"spring");
//2..构造所属行业的精准匹配查询
Predicate p2=cb.equal(custIndustry,"java");
//3.将多个查询条件组合到一起:组合(满足条件一并且满足条件二:与关系,满足条件一或满足条件二即可:或关系)
Predicate and=cb.and(p1,p2);
// cb.or();//以或的形式拼接多个查询条件
return and;
}
};
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}

/**
* 案例:完成根据客户名称的模糊匹配,返回客户列表
* 客户名称以 ’传智播客‘ 开头
*
* equal :直接的到path对象(属性),然后进行比较即可
* gt,lt,ge,le,like : 得到path对象,根据path指定比较的参数类型,再去进行比较
* 指定参数类型:path.as(类型的字节码对象)
*/
@Test
public void testLikeCustName(){
//构造查询条件
Specification spec=new Specification() {
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
//查询属性:客户名
Path<Object> custName = root.get("custName");
//查询方式:模糊匹配
Predicate predicate = cb.like(custName.as(String.class), "Spring%");
return predicate;
}
};
//模糊查询,可能有多条数据,不能使用findOne,必须使用findAll
/*List<Customer> customer=customerDao.findAll(spec);
for (Customer cust:customer) {
System.out.println(cust);
}*/
//添加排序
//创建排序对象,需要调用构造方法实例化sort对象
//第一个参数:排序的顺序(倒序,正序)
// Sort.Direction.DESC:倒序
// Sort.Direction.ASC : 升序
//第二个参数:排序的属性名称
Sort sort = new Sort(Sort.Direction.DESC,"custId");
List<Customer> list = customerDao.findAll(spec, sort);
for (Customer customer : list) {
System.out.println(customer);
}
}

5.2 基于Specifications的分页查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 分页查询
* Specification: 查询条件
* Pageable:分页参数
* 分页参数:查询的页码,每页查询的条数
* findAll(Specification,Pageable):带有条件的分页
* findAll(Pageable):没有条件的分页
* 返回:Page(springDataJpa为我们封装好的pageBean对象,数据列表,共条数)
*/
@Test
public void testPage(){
Specification spec = null;
//PageRequest对象是Pageable接口的实现类
/**
* 创建PageRequest的过程中,需要调用他的构造方法传入两个参数
* 第一个参数:当前查询的页数(从0开始)
* 第二个参数:每页查询的数量
*/
Pageable pageable=new PageRequest(0,3);

//分页查询,查询条件为null,把所有数据分页查询
Page<Customer> page=customerDao.findAll(null,pageable);
System.out.println("得到数据集合列表"+page.getContent()); //得到数据集合列表
System.out.println("得到总条数"+page.getTotalElements());//得到总条数
System.out.println("得到总页数"+page.getTotalPages());//得到总页数
}

对于Spring Data JPA中的分页查询,是其内部自动实现的封装过程,返回的是一个Spring Data JPA提供的pageBean对象。其中的方法说明如下:

1
2
3
4
5
6
//获取总页数
int getTotalPages();
//获取总记录数
long getTotalElements();
//获取列表数据
List<T> getContent();

5.3 方法对应关系

方法名称 Sql对应关系
equle filed = value
gt(greaterThan ) filed > value
lt(lessThan ) filed < value
ge(greaterThanOrEqualTo ) filed >= value
le( lessThanOrEqualTo) filed <= value
notEqule filed != value
like filed like value
notLike filed not like value

第6章 多表设计

6.1 表之间关系的划分

数据库中多表之间存在着三种关系,如图所示。

从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系
注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
明确:教程只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。

6.2 在JPA框架中表关系的分析步骤

在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以今天我们的学习重点是:掌握配置实体之间的关联关系。

第一步:首先确定两张表之间的关系。

如果关系确定错了,后面做的所有操作就都不可能正确。

第二步:在数据库中实现两张表的关系

第三步:在实体类中描述出两个实体的关系

第四步:配置出实体类和数据库表的关系映射(重点)

第7章 JPA中的一对多

7.1 示例分析

我们采用的示例为客户和联系人。

客户:指的是一家公司,我们记为A。

联系人:指的是A公司中的员工。

在不考虑兼职的情况下,公司和员工的关系即为一对多。

7.2 表关系建立

在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
什么是外键?
指的是从表中有一列,取值参照主表的主键,这一列就是外键。
一对多数据库关系的建立,如下图所示:

7.3 实体类关系建立以及映射配置

之前Customer客户表已经创建,现在创建linkMan联系人表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*创建联系人表*/
CREATE TABLE cst_linkman (
lkm_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
lkm_name varchar(16) DEFAULT NULL COMMENT '联系人姓名',
lkm_gender char(1) DEFAULT NULL COMMENT '联系人性别',
lkm_phone varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
lkm_mobile varchar(16) DEFAULT NULL COMMENT '联系人手机',
lkm_email varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
lkm_position varchar(16) DEFAULT NULL COMMENT '联系人职位',
lkm_memo varchar(512) DEFAULT NULL COMMENT '联系人备注',
lkm_cust_id bigint(32) NOT NULL COMMENT '客户id(外键)',
PRIMARY KEY (`lkm_id`),
KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* /**
* 1.实体类和表的映射关系
*
* @Eitity
* @Table 2.类中属性和表中字段的映射关系
* @Id
* @GeneratedValue
* @Column
*/
@Entity
@Table(name = "cst_customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Long custId;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_phone")
private String custPhone;
@Column(name = "cust_source")
private String custSource;


//配置客户和联系人之间的关系(一对多关系)
/**
* 使用注解的形式配置多表关系
* 1.声明关系
*
* @OneToMany : 配置一对多关系
* targetEntity :对方对象的字节码对象
* 2.配置外键(中间表)
* @JoinColumn : 配置外键
* name:外键字段名称
* referencedColumnName:参照的主表的主键字段名称
*
* * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
*/
@OneToMany(targetEntity = LinkMan.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Set<LinkMan> linkMans = new HashSet<>();
//此处省略getter和setter方法 .. ..
}

由于联系人是多的一方,在实体类中要体现出,每个联系人只能对应一个客户,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Entity
@Table(name = "cst_linkman")
public class LinkMan {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "lkm_id")
private Long lkmId; //联系人编号(主键)
@Column(name = "lkm_name")
private String lkmName;//联系人姓名
@Column(name = "lkm_gender")
private String lkmGender;//联系人性别
@Column(name = "lkm_phone")
private String lkmPhone;//联系人办公电话
@Column(name = "lkm_mobile")
private String lkmMobile;//联系人手机
@Column(name = "lkm_email")
private String lkmEmail;//联系人邮箱
@Column(name = "lkm_position")
private String lkmPosition;//联系人职位
@Column(name = "lkm_memo")
private String lkmMemo;//联系人备注

/**
* 配置联系人到客户的多对一关系
* 使用注解的形式配置多对一关系
* 1.配置表关系
* @ManyToOne : 配置多对一关系
* targetEntity:对方的实体类字节码
* 2.配置外键(中间表)
*
* * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
* fetch : 配置关联对象的加载方式
* EAGER :立即加载
* LAZY :延迟加载
*/
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
//此处省略getter和setter方法 .. ..
}

7.4 映射的注解说明

@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
@ManyToOne:
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn:
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。

7.5 一对多的操作

7.5.1 添加

ApplicationContext.xml配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?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 和springDataJpa配置-->
<!--1.创建entityManagerFactory对象交给Spring管理-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--配置的是扫描的包(实体类的所在的包)-->
<property name="packagesToScan" value="com.byliuyu.domain"/>
<!--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"/>
<!--数据库方言,支持的特有语法。比如oracle的rownum,mysql的limit-->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
<!--是否显示sql语句-->
<property name="showSql" value="true"/>
</bean>
</property>
<!--jpa的方言,高级特性,不是必须写,比如hibernate的一级缓存 二级缓存-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>


<!--注入jpa的配置信息
加载jpa的基本配置信息和jpa实现方式(hibernate)的配置信息
hibernate.hbm2ddl.auto : 自动创建数据库表
create : 每次都会重新创建数据库表
update:有表不会重新创建,没有表会重新创建表
-->
<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 name="password" value="1234"/>
<property name="jdbcUrl" value="jdbc:mysql:///jpa"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>



<!--3.整合SpringDataJpa,dao的目录-->
<jpa:repositories base-package="com.byliuyu.dao" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"/>


<!--4.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<!--5.声明式事务-->

<!--6.配置包扫描,扫描此包下的所有注解-->
<context:component-scan base-package="com.byliuyu"/>
</beans>

编写测试类:
配置了客户到联系人的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Autowired
private CustomerDao customerDao;

@Autowired
private LinkManDao linkManDao;

/**
* 保存一个客户,保存一个联系人
* 效果:客户和联系人作为独立的数据保存到数据库中
* 联系人的外键为空
* 原因?
* 实体类中没有配置关系
*/
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd() {
//创建一个客户,创建一个联系人
Customer customer = new Customer();
customer.setCustName("百度");

LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小李");

/**
* 配置了客户到联系人的关系
* 从客户的角度上:发送两条insert语句,发送一条更新语句更新数据库(更新外键)
* 由于我们配置了客户到联系人的关系:客户可以对外键进行维护
*/
customer.getLinkMans().add(linkMan);
customerDao.save(customer);
linkManDao.save(linkMan);
}

配置联系人到客户的关系(多对一)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd1() {
//创建一个客户,创建一个联系人
Customer customer = new Customer();
customer.setCustName("百度");

LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小李");

/**
* 配置联系人到客户的关系(多对一)
* 只发送了两条insert语句
* 由于配置了联系人到客户的映射关系(多对一)
*
*
*/
linkMan.setCustomer(customer);
customerDao.save(customer);
linkManDao.save(linkMan);
}

配置客户到联系人和联系人到客户关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 此案例会有一条多余的update语句
* * 由于一的一方可以维护外键:会发送update语句
* * 解决此问题:只需要在一的一方放弃维护权即可
*
*/
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd2() {
//创建一个客户,创建一个联系人
Customer customer = new Customer();
customer.setCustName("百度");

LinkMan linkMan = new LinkMan();
linkMan.setLkmName("小李");

linkMan.setCustomer(customer);//由于配置了多的一方到一的一方的关联关系(当保存的时候,就已经对外键赋值)
customer.getLinkMans().add(linkMan);//由于配置了一的一方到多的一方的关联关系(发送一条update语句)
customerDao.save(customer);
linkManDao.save(linkMan);
}

通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权, 放弃外键维护权的配置将如下配置改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 放弃外键维护权
* mappedBy:对方配置关系的属性名称
* cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
* CascadeType.all : 所有
* MERGE :更新
* PERSIST :保存
* REMOVE :删除
*
* fetch : 配置关联对象的加载方式
* EAGER :立即加载
* LAZY :延迟加载

*/
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)

7.5.2 删除

删除操作的说明如下:
删除从表数据:可以随时任意删除。
删除主表数据:

  • 有从表数据
    1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。
    2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null,没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
    3、如果还想删除,使用级联删除引用
  • 没有从表数据引用:随便删

在实际开发中,级联删除请慎用!(在一对多的情况下)

7.5.3 级联操作

级联操作:指操作一个对象同时操作它的关联对象
级联操作:

1.需要区分操作主体
2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
使用方法:只需要在操作主体的注解上配置cascade

级联添加:保存一个客户的同时,保存客户的所有联系人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 级联添加:保存一个客户的同时,保存客户的所有联系人
* 需要在操作主体的实体类上,配置casacde属性
*/
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testCascadeAdd() {
//创建客户对象
Customer customer=new Customer();
customer.setCustName("java1");
//创建联系人对象
LinkMan linkMan=new LinkMan();
linkMan.setLkmName("Spring1");

linkMan.setCustomer(customer);
customer.getLinkManSet().add(linkMan);

//这里只保存了客户,联系人会根据级联关系自动保存
customerDao.save(customer);
}

同时,还需要在Customer实体,多表映射注解中添加cascade属性

1
2
3
4
5
6
7
8
9
/**        (可以配置到设置多表的映射关系的注解上)
* CascadeType.all : 所有
* MERGE :更新
* PERSIST :保存
* REMOVE :删除
* 推荐配置成:CascadeType.ALL
*/
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
private Set<LinkMan> linkManSet=new HashSet<LinkMan>();

级联删除:删除一个客户的同时,删除客户的所有联系人
需要先把注入jpa配置的create改成update

1
2
3
4
5
6
7
8
9
10
11
<!--注入jpa的配置信息
加载jpa的基本配置信息和jpa实现方式(hibernate)的配置信息
hibernate.hbm2ddl.auto : 自动创建数据库表
create : 每次都会重新创建数据库表
update:有表不会重新创建,没有表会重新创建表
-->
<property name="jpaProperties" >
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 级联删除:
* 删除1号客户的同时,删除1号客户的所有联系人
*/
@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testCascadeRemove() {
//1。查询1号客户
customerDao.findOne(1L);
//2.删除1号客户
customerDao.delete(1L);
}

第8章 JPA中的多对多

8.1 示例分析

我们采用的示例为用户和角色。

用户:指的是咱们班的每一个同学。

角色:指的是咱们班同学的身份信息。

比如A同学,它是我的学生,其中有个身份就是学生,还是家里的孩子,那么他还有个身份是子女。

同时B同学,它也具有学生和子女的身份。

那么任何一个同学都可能具有多个身份。同时学生这个身份可以被多个同学所具有。

所以我们说,用户和角色之间的关系是多对多。

8.2 表关系建立

多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:

8.3 实体类关系建立以及映射配置

一个用户可以具有多个角色,所以在用户实体类中应该包含多个角色的信息,代码如下:

未完,待补充

文章作者: LiuYu
文章链接: https://www.liuyu.pw/posts/cdeb19ab.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LiuYuBLOG

评论