spring:

  • 6

  • Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依 赖注⼊功能。 Spring Aspects : 该模块为与AspectJ的集成提供⽀持。

  • Spring AOP :提供了⾯向切⾯的编程实现。

  • Spring JDBC : Java数据库连接。

  • Spring JMS :Java消息服务。

  • Spring ORM : ⽤于⽀持Hibernate等ORM⼯具。

  • Spring Web : 为创建Web应⽤程序提供⽀持。

  • Spring Test : 提供了对 JUnit 和 TestNG 测试的⽀持

概述:

Spring 是个java企业级应用的开源开发框架。

Spring 是一个轻量级的框架、最基础的版本只有2MB左右

Spring主要用来开发Java应用,但是在Java EE 平台上构建web应用程序是需要进行扩展的。

Spring 框架目标是简化Java企业级应用开发,减少开发难度和周期,并通过POJO为基础的编程模型促进良好的编程习惯。

特点:

轻量级:

从大小与开销,Spring都是轻量级的,完整的Spring框架可以在只有1M多的JAR文件里面进行发布

非侵入式的: 基于Spring 开发中的对象不依赖于Spring中的API

控制反转(IOC -Inversion of control):

ji
目的: 促进耦合

将对象的创建权交给Spring容器去创建,当一个对象依赖其他对象,会通过被动的方式传递进来,而不是通过这个对象创建或者查找依赖的对象

面向切面(Aspect Oriented Programming——AOP)

​ Spring支持面向切面编程,并且把业务逻辑和系统服务分开

容器

spring 包含并管理应用中的对象的生命周期和配置。可以配置每个bean是如何被创建的—基于一个可配置的原型。所配置的bean能够单独的创建一个实例,或者每次生成一个新的实例

框架集合

image-20220120003152978

Spring BeanFactory 容器

Spring 有两种类型bean,一种是普通bean,另一种是工厂bean(FactoryBean)

普通bean: 在配置文件中定义bean类型就是返回类型

工厂bean:在配置文件定义bean可以和返回类型不一样

创建类,作为工厂bean 实现FactoryBean

实现接口里的方法,在实现的方法中定义返回的bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyBean implements FactoryBean<Course> {

//定义返回bean
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}

@Override
public Class<?> getObjectType() {
return null;
}

@Override
public boolean isSingleton() {
return false;
}
}

Spring ApplicationContext 容器

Spring Bean 作用域

在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton

Spring 框架支持以下五个作用域,分别为 singleton、prototype、request、session 和 global session,5种作用域说明如下所示,

Spring默认是单实例对象

image-20220116111935725

通过(scope)设置单实例还是多实例:

作用域 描述
singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值,程序加载时就进行创建
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean() ,每次调用都进行创建
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session 一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境

image-20220116112314095

image-20220116112458490

1
2
3
4
5
6
7
8
    <!--创建多个course对象-->
<bean id="course1" class="collectiontype.Course" scope="singleton">
<property name="cname" value="Spring5框架"></property>
</bean>

<!-- <bean id="course1" class="collectiontype.Course" scope="prototype">-->
<!-- <property name="cname" value="Spring5框架"></property>-->
<!-- </bean>-->

Spring Bean 生命周期

当一个bean被实例化,他可能需要执行一些初始化的工作,使他转换为可用的状态

init-method 容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法
destroy-method 容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
lazy-init 懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效

Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁

Spring的执行流程:

Bean的生命周期

Bean 生命周期的整个执行过程描述如下。

  1. Spring 启动,查找并加载需要被 Spring 管理的 Bean,并实例化 Bean。
  2. 利用依赖注入完成 Bean 中所有属性值的配置注入。
  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
  4. 2222222
  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  6. 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
  10. 如果在 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

为了定义安装和拆卸一个 bean,我们只要声明带有 init-method 和/或 destroy-method 参数的 。init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法。

以下进行实现:

1、通过构造器创建bean实例(无参构造),

2、为bean设置属性值和其他bean的引用

3、调用bean的初始化方法

4、获取bean对象

5、当容器关闭,调用bean的销毁方法

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

public class Orders {

//无参数构造
public Orders() {
System.out.println("第一步 执行无参数构造创建bean实例");
}

private String oname;
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用set方法设置属性值");
}

//创建执行的初始化的方法
public void initMethod() {
System.out.println("第三步 执行初始化的方法");
}

//创建执行的销毁的方法
public void destroyMethod() {
System.out.println("第五步 执行销毁的方法");
}
}

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="orders" class="demon02.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>

</beans>
1
2
3
4
5
6
7
8
    @Test
public void TestBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
Orders orders= context.getBean("orders", Orders.class);
System.out.println("第四步: 获取创建的bean实例对象");
// 手动让bean实例销毁
((ClassPathXmlApplicationContext) context).close();
}

image-20220116121536675

Spring依赖注入:

Spring创建对象,注入

什么是bean管理

(1)Spring创建对象

(2)Spring 进行属性注入

bean管理有两个方式

(1)基于xml配置文件方式实现

(1)在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建

(2)在 bean 标签有很多属性,介绍常用的属性

  • id 属性:唯一标识
  • class 属性:类全路径(包类路径)

(2) 基于注解方式进行实现

基于set构造函数的依赖注入

book实体类创建:

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
/**
* 演示使用set方法进行注入属性
*/
public class Book {
//创建属性
private String bname;
private String bauthor;
private String address;
//创建属性对应的set方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}

public void setAddress(String address) {
this.address = address;
}

public void testDemo() {
System.out.println(bname+"::"+bauthor+"::"+address);
}
}

bean文件配置(特殊符号以及空值注入):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <bean id ="book" class="Demon01.bean.Book">
<!--使用property完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bname" value="金"></property>
<property name="bauthor" value="金"></property>

<!--null值-->
<!--<property name="address">
<null/>
</property>-->

<!--属性值包含特殊符号
1 把<>进行转义 &lt; &gt;
2 把带特殊符号内容写到CDATA
-->
<property name="address">
<value><![CDATA[<<南京>>]]></value>
</property>
</bean>

test

1
2
3
4
5
6
@Test
public void testBean2() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
book.testDemo();
}

image-20220115103535881

p名称空间注入:

首先添加p名称空间在配置文件中

1
2
3
4
5
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

进行属性注入:

1
2
<!--2 set方法注入属性-->
<bean id="book1" class="Demon01.bean.Book" p:bname="九阳神功" p:bauthor="无名氏"></bean>

基于构造函数的依赖注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 * 使用有参数构造注入
*/
public class Orders {
//属性
private String oname="";
private String address;
//有参数构造
public Orders(String oname,String address) {
this.oname = oname;
this.address = address;
}

public void ordersTest() {
System.out.println(oname+"::"+address);
}
}
1
2
3
4
5
6
<!--    有参构造注入属性-->
<bean id ="orders" class="Demon01.bean.Orders">
<constructor-arg name="oname" value="电脑"></constructor-arg>
<constructor-arg name="address" value="China"></constructor-arg>
</bean>

image-20220115103936695

spring注入外部bean

通过三层结构,Dao\service,在service调用dao层的方法,在service中调用dao层的方法,ref 进行属性的级联操作

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserService {

//创建UserDao类型属性,生成set方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

public void add() {
System.out.println("service add...............");
userDao.update();
}
}
1
2
3
4
5
6
7
8
9

public class UserDaoImpl implements UserDao {

@Override
public void update() {
System.out.println("dao update...........");
}
}

1
2
3
4
5
6
7
8
9
    <bean id="userService" class="Demon01.service.UserService"  p:userDao-ref="UserDaoImpl">
<!-- 注入userDao对象
name属性:类里面属性名称
ref属性:创建userDao对象bean标签id值
-->
<!-- <property name="userDao" ref="UserDaoImpl"></property>-->

</bean>
<bean id="UserDaoImpl" class="Demon01.dao.UserDaoImpl"></bean>

test

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testBean1() {
//1 加载spring配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("bean2.xml");

//2 获取配置创建的对象
UserService userService = context.getBean("userService", UserService.class);

userService.add();
}

spring注入内部bean以及级联操作:

一对多关系:部门和员工的关系,一个部门之中有多个员工

在实体类中表示一对多关系,员工表示所属部门,使用对象的属性类型进行表示

部门类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}

@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}

员工类

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
//员工类
public class Emp {
private String ename;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Dept dept;

//生成dept的get方法
public Dept getDept() {
return dept;
}

public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}

public void add() {
System.out.println(ename+"::"+gender+"::"+dept);
}
}

xml配置:

1
2
3
4
5
6
7
8
9
10
11
12
    <bean id="emp" class="Demon01.bean.Emp">
<!-- 设置属性值-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!-- 设置对象属性-->
<property name="dept">
<bean id="dept" class="Demon01.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>

image-20220115174947408

级联操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<!--级联赋值-->
<bean id="emp2" class="Demon01.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept" class="Demon01.bean.Dept">
<property name="dname" value="财务部"></property>
</bean>

image-20220115175907112

注入集合属性:

1
2
3
4
5
6
7
8
9
10
11
//1 数组类型属性
private String[] courses;
//2 list集合类型属性
private List<String> list;
//3 map集合类型属性
private Map<String,String> maps;
//4 set集合类型属性
private Set<String> sets;

//学生所学多门课程
private List<Course> courseList;
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
<!--1 集合类型属性注入-->
<bean id="stu" class="collectiontype.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<!--map类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
<!--注入list集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<!--创建多个course对象-->
<bean id="course1" class="collectiontype.Course">
<property name="cname" value="Spring5框架"></property>
</bean>
<bean id="course2" class="collectiontype.Course">
<property name="cname" value="MyBatis框架"></property>
</bean>

测试:

1
2
3
4
5
6
@Test
public void testCollection1() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Stu stu = context.getBean("stu", Stu.class);
stu.test();
}

结果:

image-20220114174147438

把集合注入的部分提取出来

(1)在spring配置文件中引入名称空间util

1
2
3
4
5
6
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
1
2
3
4
5
6
7
8
9
10
11
<!--1 提取list集合类型属性注入, 提取出公共的部分-->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>

<!--2 提取list集合类型属性注入使用-->
<bean id="book" class="collectiontype.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12

public class Book {
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}

public void test() {
System.out.println(list);
}
}

image-20220115181337603

Spring Beans自动装配

bean之间的依赖关系之间的注入,Spring 容器支持多种装配 Bean 的方式,如基于 XML 的 Bean 装配、基于 Annotation 的 Bean 装配和自动装配等。

自动装配就是指 Spring 容器在不使用 标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。

使用自动装配需要配置 元素的 autowire 属性。autowire 属性有五个值,具体说明如下表所示。

名称 说明
no 默认值,表示不使用自动装配,Bean 依赖必须通过 ref 元素定义。
byName 根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。
byType 根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。
constructor 类似于 byType,根据构造方法参数的数据类型,进行 byType 模式的自动装配。
autodetect(3.0版本不支持) 如果 Bean 中有默认的构造方法,则用 constructor 模式,否则用 byType 模式。
1
2
3
4
5
6
7
public class Dept {
@Override
public String toString() {
return "Dept{}";
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}

@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}

public void test() {
System.out.println(dept);
}
}

不使用bean的时候会进行一个级联的操作

1
2
3
4
5
6
7
8
9
10
11
    <!--实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常用两个值:
byName根据属性名称注入 ,注入值bean的id值和类属性名称一样
byType根据属性类型注入 class
-->
<bean id="emp" class="demon02.autowire.Emp" autowire="byType">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="demon02.autowire.Dept"></bean>

自动装配的优缺点

优点

  • 自动装配只需要较少的代码就可以实现依赖注入。

缺点

  • 不能自动装配简单数据类型,比如 int、boolean、String 等。
  • 相比较显示装配,自动装配不受程序员控制。

使用Bean管理xml方式(外部属性文件)

创建jdbc.properties文件

1
2
3
4
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.userName=root
prop.password=root

bean.xml中引入外部文件

xml中需要引入名称空间:

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

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

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
<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!--直接配置连接池-->
<!--<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/userDb"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>-->

<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>

</beans>

基于注解的配置:

在 Spring 中,尽管可以使用 XML 配置文件实现 Bean 的装配工作,但如果应用中 Bean 的数量较多,会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

Java 从 JDK 5.0 以后,提供了 Annotation(注解)功能,Spring 2.5 版本开始也提供了对 Annotation 技术的全面支持,我们可以使用注解来配置依赖注入。

Spring 默认不使用注解装配 Bean,因此需要在配置文件中添加 context:annotation-config/,启用注解。

对象创建注解:

1)@Component

可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。

2)@Repository

用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

3)@Service

通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

4)@Controller

通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

引入依赖:

image-20220116220153982

xml配置中需要引入

1
xmlns:context="http://www.springframework.org/schema/context"
1
2
3
4
5
6
7
8
9
10
11
12
<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启组件扫描-->
<context:component-scan base-package="demon03"></context:component-scan>
</beans>

相当于读取bean的类项

image-20220116221450771 image-20220116221805693

image-20220116221841242zuzu

组件扫描配置:

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
<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启组件扫描
1. 如果扫描多个包 ,多个包使用逗号隔开
2. 扫描包上层目录
-->
<context:component-scan base-package="demon03"></context:component-scan>

<!--
use-default-filters 表示现在不使用默认filter,自己配置filter
context:included-filter 设置扫描哪些内容
-->
<context:component-scan base-package="demon03" use-default-filters="false">
<!-- 只会扫描该包中的该注解-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

<!--
除了该包中的该注解,其他的都会进行扫描
context:exclude-filter
-->
<context:component-scan base-package="demon03">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

</beans>

基于注解方式进行属性注入

序号 注解 & 描述
1 @Required@Required 注解应用于 bean 属性的 setter 方法。
2 @Autowired@Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。
3 @Qualifier与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。@Autowired 和 @Qualifier 注解可以用来删除混乱。(当使用@Autowired 进行属性注入的时候,如果有多个实现类的话,使用 @Qualifier进行具体指明实现哪个)
4 JSR-250 AnnotationsSpring 支持 JSR-250 的基础的注解,其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解。
5 @Resource 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 实例名称进行装配。Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配。如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。
6 @Value 注入固定的值

@Qualifier与@Autowired 演示:

创建 UserDao接口

1
2
3
4
5
6
public interface UserDao {

public void add();

}

实现UserDaoImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

import org.springframework.stereotype.Repository;

@Repository(value = "userDaoImpl1")
public class UserDaoImpl implements UserDao{


@Override
public void add() {

System.out.println("dao add .......");

}
}

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
package demon03.service;

import demon03.dao.UserDao;
import demon03.dao.UserDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

//在注解里面value属性值可以省略不写,
//默认值是类名称,首字母小写
//UserService -- userService
@Component(value = "userService") //<bean id="userService" class=".."/>
public class UserService {

// 定义dao类型属性
// 不需要再添加 set方法
// 添加注入属性注解,根据属性类型进行注入 ,如果属性中有多个实现类,需要进行指定具体的类型

@Autowired
@Qualifier(value = "userDaoImpl1")
private UserDao userDao;

public void add() {
System.out.println("service add.......");
userDao.add();
}
// 不使用注解的情况
//
// private UserDaoImpl userDao;
//
// public void setUserDao(UserDaoImpl userDao) {
// this.userDao = userDao;
// }
//
// public void add() {
// setUserDao(new UserDaoImpl());
// System.out.println("service add.......");
// userDao.add();
//
// }
}

test:

1
2
3
4
5
6
7
8
9
10
11
    @Test
public void TestBean(){
// 加载配置文件,获取扫描的配置包
ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
// 获取文件的配置项
UserService userService= context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();

}

@Resource

不是Spring官方进行提供的

1
import javax.annotation.Resource;
image-20220116233032003

完全注解开发:

不需要使用配置文件,创建配置类取代xml文件

1
2
3
4
5
@Configurable//作为配置类,替代xml
@ComponentScan(basePackages = "demon03")
public class SpringConfig {

}
1
2
3
4
5
6
7
8
9
10
    @Test
public void TestSpringConfig(){
// 加载配置文件,获取扫描的配置包
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 获取文件的配置项
UserService userService= context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();

}

AOP:

image-20220120003241588

参考:

彻底征服 Spring AOP 之 理论篇 - SegmentFault 思否

AOP全称是”Aspect Oriented Programming“ ,即面向切面编程。

AOP 采取横向抽取机制(动态代理),取代传统得纵向继承机制的重复性代码,主要体现在事务处理、日志管理、权限控制、异常处理等方面

AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用。

所谓”切面”,简单说就是那些与业务无关,却为业务模块所共 同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性

目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。

应用场景:

Authentication 权限
Caching 缓存
Context passing 内容传递
Error handing 错误处理
Lazy loading 懒加载
Debugging 调试
logging trancing profiling and monitoring 记录、跟踪、优化、校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
名称 说明
Joinpoint(连接点) 指那些被拦截到的点,在 Spring 中,就是指被拦截到的方法
Pointcut(切入点) 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。对拦截点进行定义
Advice(通知) 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
Target(目标) 指代理的目标对象。
Weaving(植入) 指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理) 指生成的代理对象。
Aspect(切面) 切入点和通知的结合。对横切关注点的抽象
横切关注点 对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

Advice 直译为通知,也有的资料翻译为“增强处理”,共有 5 种类型,如下表所示。

通知 说明
before(前置通知) 通知方法在目标方法调用之前执行
after(后置通知) 通知方法在目标方法返回或异常后调用
after-returning(返回后通知) 通知方法会在目标方法返回后调用
after-throwing(抛出异常通知) 通知方法会在目标方法抛出异常后调用
around(环绕通知) 通知方法会将目标方法封装起来

Spring AOP 和 AspectJ

基本概念

​ “横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块, 并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共 同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未 来的可操作性和可维护性。

​ 使用”横切”技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流 程是核心 关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生 在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统 中的各种关注点,将核心关注点和横切关注点分离开来。

Spring AOP 是基于 AOP 编程模式的一个框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。有两种实现方式:基于接口的 JDK 动态代理基于继承的 CGLIB 动态代理

AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。

Spring AOP和AspectJ AOP有什么区别?

Spring AOP是属于运行时增强,而AspectJ是编译时增强。

Spring AOP基于代理(Proxying),而 AspectJ基于字节码操作(Bytecode Manipulation)。

Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。

AspectJ相 比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比 SpringAOP快很多。

Spring AOP:

两种代理的方式:

  • 基于接口的 JDK 动态代理

  • 基于继承的 CGLIB (Code Generation Library)动态代理

Spring 提供两种方式来生成代理对象 ,具体使用哪种方式生成由AopProxyFactory 根据AdvisedSupport对象的配置来进行决定。默认策略是,如果目标类是接口,就使用JDK动态代理技术,否则使用Cglib来生成代理

JDK代理和CGLIB代理的区别

JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。而 CGLIB 动态代理是利用 ASM 开源包,加载代理对象类的 class 文件,通过修改其字节码生成子类来处理。

JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。

CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不能声明成 final 类型。

JDK动态代理特点
  • 代理对象必须实现一个或多个接口
  • 以接口的形式接收代理实例,而不是代理类
CGLIB动态代理特点
  • 代理对象不能被 final 修饰
  • 以类或接口形式接收代理实例
JDK与CGLIB动态代理的性能比较

生成代理实例性能:JDK > CGLIB
代理实例运行性能:JDK > CGLIB

JDK 动态接口代理

JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。

Spring JDK 动态代理需要实现 InvocationHandler 接口,重写 invoke 方法,客户端使用 Java.lang.reflect.Proxy 类产生动态代理类的对象。

1、使用 Proxy 类里面的方法创建代理对象

​ 创建接口,定义方法

1
2
3
4
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}

​ 创建接口的实现类,实现方法

1
2
3
4
5
6
7
8
9
10
11
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public String update(String id) {
return id;
}
}

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
package proxy.test;

import proxy.dao.UserDao;
import proxy.dao.UserDaoImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class JDKProxy {
public static void main(String[] args) {
// 创建接口的实现类的对象
Class[] interfaces ={UserDao.class};
UserDaoImpl userDao =new UserDaoImpl();
// 返回调用代理类的实例
UserDao useDao= (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao));
int add = useDao.add(1, 2);
System.out.println("add = " + add);

}
// 创建代理对象代码
}
class UserDaoProxy implements InvocationHandler{
// 创建是谁的对象就把谁传递进来

//有参数构造传递
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));
//被增强的方法执行
Object res = method.invoke(obj, args);

//方法之后
System.out.println("方法之后执行...."+obj);

return res;
}

}

image-20220117175114367


基于继承的 CGLIB (Code Generation Library)动态代理

JDK 动态代理使用起来非常简单,但是 JDK 动态代理的目标类必须要实现一个或多个接口,具有一定的局限性。如果不希望实现接口,可以使用 CGLIB代理。

AspectJ:

名称 说明
@Aspect 用于定义一个切面,
@Pointcut 用于定义一个切入点,在哪个地方进行切入
@Before 用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning 用于定义后置通知,相当于 AfterReturningAdvice。
@Around 用于定义环绕通知,相当于MethodInterceptor。
@AfterThrowing 用于定义抛出通知,相当于ThrowAdvice。
@After 用于定义最终final通知,不管是否异常,该通知都会执行。
@DeclareParents 用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。
  • 基于xml配置文件实现

  • 基于注解方式实现

切入点表达式:

execution (权限修饰符 返回类型 类路径 方法名称(【参数列表】))

对 com.dao.BookDao 类里面的 add 进行增强

1
execution(* com.dao.BookDao.add(..))

对 com.dao.BookDao 类里面的 所有方法 进行增强

1
execution(* com.dao.BookDao.*(..))

对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强

1
execution(* com.dao.*.* (..))

基于注解测试:

image-20220314043801361

导入所需要的依赖

image-20220117180710678

image-20220117180721676

注解配置:

  • 开启注解扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="proxy.*"></context:component-scan>

<!-- 开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 依赖注入
1
2
3
4
5
6
7
8
9
10
11
12

import org.springframework.stereotype.Component;

//被增强的类
@Component
public class User {
public void add() {
//模拟异常的抛出
int i=10/0;
System.out.println(" add ");
}
}
  • UserProxy 进行模拟
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
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

//生成代理类的对象
//增强的类
@Component
@Aspect //生成代理对象
public class UserProxy {
// 相同切入点进行抽取
@Pointcut(value = "execution(* proxy.aopanno.User.add(..))")
public void pointDemo(){
}


// 前置通知
@Before(value = "pointDemo()")
public void before() {
System.out.println("before.........");
}

//后置通知(返回通知)
@AfterReturning(value = "execution(* proxy.aopanno.*.*(..))")
public void afterReturning() {
System.out.println("afterReturning.........");
}

// 最终通知,通知方法在目标方法返回或异常后调用,cy
@After(value = "execution(* proxy.aopanno.User.add(..))")
public void after() {
System.out.println("after.........");
}

//异常通知
@AfterThrowing(value = "execution(* proxy.aopanno.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.........");
}

@Around(value = "execution(* proxy.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前");
// 被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后执行");


}

}
}

  • 开启主类配置
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = {"proxy.*"})
//开启切面
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}

正常时:

image-20220117234958593

出错时

image-20220117234703014

当有多个增强类对同一个方法进行增强时,设置增强类的优先级

在增强类添加注解 @Order(数字类型的值),数字类型值越小,优先级越高

1
2
3
4
5
6
7
8
9
10
11
12
@Component
@Aspect //生成代理对象
//数字类型值越小,优先级越高
@Order(1)
public class PersonProxy {
// 前置通知
@Before(value = "execution(* proxy.aopanno.*.*(..))")
public void before() {
System.out.println("before1.........");
}
}

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
mport org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//生成代理类的对象
//增强的类
@Component
@Aspect //生成代理对象
@Order(3)
public class UserProxy {
// 相同切入点进行抽取
@Pointcut(value = "execution(* proxy.aopanno.User.add(..))")
public void pointDemo(){
}


// 前置通知
@Before(value = "pointDemo()")
public void before() {
System.out.println("before3.........");
}

//后置通知(返回通知)
@AfterReturning(value = "execution(* proxy.aopanno.*.*(..))")
public void afterReturning() {
System.out.println("afterReturning.........");
}

// 最终通知,通知方法在目标方法返回或异常后调用,cy
@After(value = "execution(* proxy.aopanno.User.add(..))")
public void after() {
System.out.println("after.........");
}

//异常通知
@AfterThrowing(value = "execution(* proxy.aopanno.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.........");
}

@Around(value = "execution(* proxy.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前");
// 被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后执行");


}
}

image-20220118000406587

基于xml配置:

基于 XML 的声明式是指通过 Spring 配置文件的方式来定义切面、切入点及通知,而所有的切面和通知都必须定义在 aop:config 元素中。

AOP操作(AspectJ 配置文件)

创建被增强类

1
2
3
4
5
6
7
8

public class Book {
public void buy() {
System.out.println("buy.............");
}
}


创建增强类

1
2
3
4
5
6
public class BookProxy {
public void before() {
System.out.println("before.........");
}
}

配置文件:

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
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 创建bean对象-->
<bean id="book" class="proxy.aopxml.Book"></bean>
<bean id="bookProxy" class="proxy.aopxml.BookProxy"></bean>
<!-- 配置aop增强-->
<aop:config>
<!-- 切入点 对哪个包哪个方法进行增强-->
<aop:pointcut id="p" expression="execution(* proxy.aopxml.Book.buy(..) )"/>

<!-- 配置切面-->
<!-- 那个bean是切面-->
<aop:aspect ref="bookProxy">
<!-- 将切面指定在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>

</aop:config>

</beans>


image-20220118001910244

应用场景

  1. image-20220314045554713

Spring 使用 JdbcTemplate

引入操作所需要的jar包:

image-20220120002855113

image-20220120003328962

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
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 开启注解扫描 -->
<context:component-scan base-package="JdbcTemplate.*"></context:component-scan>



<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>


<!-- &lt;!&ndash;配置连接池&ndash;&gt;-->
<!-- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">-->
<!-- <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>-->
<!-- <property name="url">-->
<!-- <value>jdbc:mysql://localhost:3306/wz?useSSL=false&amp;serverTimezone=UTC</value>-->
<!-- </property>-->
<!-- <property name="username" value="root"></property>-->
<!-- <property name="password" value="jin200727"></property>-->
<!-- </bean>-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
1
2
3
4
prop.driverClass=com.mysql.cj.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/wz?useSSL=false&serverTimezone=UTC
prop.userName=root
prop.password=jin200727
1
2
3
4
5
6
7
8
9
10
11
12
13
    @org.junit.Test
public void TestConnection() throws SQLException {

ApplicationContext context =
new ClassPathXmlApplicationContext("bean8.xml");
DataSource bean = context.getBean(DataSource.class);
Connection connection = bean.getConnection();
System.out.println("connection = " + connection);
connection.close();
// ArticleService articleService = context.getBean("articleServiceImpl", ArticleService.class);/

// System.out.println("article = " + articleService.selectById(544));
}

Spring事务(Transaction)

事务是一组由逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么执行,要么都不执行

事务的四个关键属性

原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。

一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚

隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰

持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

编程式和声明式

  1. 传统的编程式事务管理,即通过编写代码实现的事务管理;
  2. 基于 AOP 技术实现的声明式事务管理。

1. 编程式事务管理

编程式事务管理是通过编写代码实现的事务管理,灵活性高,但难以维护。

2. 声明式事务管理

Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

Spring 实现声明式事务管理主要有 2 种方式:

  • 基于 XML 方式的声明式事务管理。
  • 通过 Annotation 注解方式的事务管理。

事务管理接口

PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 是事务的 3 个核心接口。

PlatformTransactionManager接口

PlatformTransactionManager 接口用于管理事务,接口定义如下:

1
2
3
4
5
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}

该接口中方法说明如下:

名称 说明
TransactionStatus getTransaction(TransactionDefinition definition) 用于获取事务的状态信息
void commit(TransactionStatus status) 用于提交事务
void rollback(TransactionStatus status) 用于回滚事务

在项目中,Spring 将 xml 中配置的事务信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。

TransactionDefinition接口

TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下。

1
2
3
4
5
6
7
public interface TransactionDefinition {
int getPropagationBehavior();
int getIsolationLevel();
String getName();
int getTimeout();
boolean isReadOnly();
}

该接口中方法说明如下。

方法 说明
String getName() 获取事务的名称
int getIsolationLevel() 获取事务的隔离级别
int getPropagationBehavior() 获取事务的传播行为
int getTimeout() 获取事务的超时时间
boolean isReadOnly() 获取事务是否只读

以下是隔离级别的值。

方法 说明
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
ISOLATION_READ_COMMITTED (Oracle 默认级别)允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
ISOLATION_REPEATABLE_READ (MySQL 默认级别),多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
ISOLATION_SERIALIZABLE 完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读

以下是传播行为的可能值,传播行为用来控制是否需要创建事务以及如何创建事务。

名称 说明
PROPAGATION_MANDATORY 支持当前事务,如果不存在当前事务,则引发异常
PROPAGATION_NESTED 如果当前事务存在,则在嵌套事务中执行
PROPAGATION_NEVER 不支持当前事务,如果当前事务存在,则引发异常
PROPAGATION_NOT_SUPPORTED 不支持当前事务,始终以非事务方式执行
PROPAGATION_REQUIRED 默认传播行为,支持当前事务,如果不存在,则创建一个新的
PROPAGATION_REQUIRES_NEW 创建新事务,如果已经存在事务则暂停当前事务
PROPAGATION_SUPPORTS 支持当前事务,如果不存在事务,则以非事务方式执行

TransactionStatus接口

TransactionStatus 接口提供了一些简单的方法来控制事务的执行和查询事务的状态,接口定义如下。

1
2
3
4
5
6
7
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}

image-20220120160746838

Spring编程式事务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Tx.dao;

import Tx.model.User;

import java.util.List;

public interface UserDao {
// * 初始化User表
// */
void createUserTable();
/**
* 保存用户
*/
void saveUser(User user);
/**
* 查询用户
*/
List<User> listUser();

}

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
package Tx.model;

public class User {
private int id;
private String name;
private int age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}

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 int getAge() {
return age;
}

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

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
package Tx.dao;

import Tx.dao.UserDao;
import Tx.model.User;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
private UserDao userDao;
private PlatformTransactionManager transactionManager;

@Override
public void createUserTable() {
this.jdbcTemplate.execute("CREATE TABLE `user` (\r\n" + " `id` int(11) NOT NULL AUTO_INCREMENT,\r\n"
+ " `name` varchar(50) DEFAULT NULL,\r\n" + " `age` int(11) DEFAULT NULL,\r\n"
+ " PRIMARY KEY (`id`)\r\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
}

@Override
public void saveUser(User user) {
// 定义事务并获取事务的实例
TransactionDefinition def = new DefaultTransactionDefinition();
// getTransaction用于启动事务,返回Transaction实例对象
TransactionStatus status = transactionManager.getTransaction(def);
this.jdbcTemplate.update("INSERT INTO USER(NAME,age) VALUES (?,?)", user.getName(), user.getAge());
transactionManager.commit(status);

}

@Override
public List<User> listUser() {
List<User> users = this.jdbcTemplate.query("SELECT NAME,age FROM USER", new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
}
});
return users;
}

public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}

public UserDao getUserDao() {
return userDao;
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}

public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setDataSource(DataSource datasource) {
this.jdbcTemplate = new JdbcTemplate(datasource);
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package Tx;

import Tx.dao.UserDao;
import Tx.model.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MainApp {

public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("tx.xml");
UserDao dao = (UserDao) ctx.getBean("userdao");
// dao.createUserTable();
// dao.saveUser(new User("bianchengbang", 12));
// dao.saveUser(new User("baidu", 18));
List<User> users = dao.listUser();
for (User user : users) {
System.out.println("姓名:" + user.getName() + "\t年龄:" + user.getAge());
}
}
}

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
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">


<!-- 配置数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动 -->
<property name="driverClassName"
value="com.mysql.cj.jdbc.Driver" />
<!--连接数据库的url -->
<property name="url" value="jdbc:mysql://localhost:3306/wz?useSSL=false&amp;serverTimezone=UTC" />
<!--连接数据库的用户名 -->
<property name="username" value="root" />
<!--连接数据库的密码 -->
<property name="password" value="jin200727" />
</bean>

<!--配置JDBC模板 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<!--默认必须使用数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>

<!-- 配置事务管理-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--配置dao层接口-->
<bean id="userdao" class="Tx.dao.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
<property name="transactionManager" ref="transactionManager" />
</bean>

</beans>

​ 使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余

声明式事务管理

大多数情况下,声明式事务比编程式事务管理更好,它将事务的管理代码从业务中分离出来,以声明的方式来实现事务

事务管理代码的固定模式作为一种横向的关注点,可以通过AOP的方式进行模块化

Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。

Spring既支持编程式事务管理,也支持声明式的事务管理。

1.1 Spring提供的事务管理器

Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。

Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

事务管理器可以以普通的bean的形式声明在Spring IOC容器中。

1.2 事务管理器的主要实现

①DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。

②JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理

③HibernateTransactionManager:用Hibernate框架存取数据库

img

测试数据准备:

1.1 需求

img

  1. 2数据库表

    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
    CREATE TABLE book (
    isbn VARCHAR (50) PRIMARY KEY,
    book_name VARCHAR (100),
    price INT
    ) ;
    CREATE TABLE book_stock (
    isbn VARCHAR (50) PRIMARY KEY,
    stock INT,
    CHECK (stock > 0)
    ) ;

    CREATE TABLE account (
    username VARCHAR (50) PRIMARY KEY,
    balance INT,
    CHECK (balance > 0)
    ) ;

    INSERT INTO account (`username`,`balance`) VALUES ('Tom',100000);
    INSERT INTO account (`username`,`balance`) VALUES ('Jerry',150000);

    INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-001','book01',100);
    INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-002','book02',200);
    INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-003','book03',300);
    INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-004','book04',400);
    INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-005','book05',500);

    INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-001',1000);
    INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-002',2000);
    INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-003',3000);
    INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-004',4000);
    INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-005',5000);

配置文件:

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
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">


<!-- 配置数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动 -->
<property name="driverClassName"
value="com.mysql.cj.jdbc.Driver" />
<!--连接数据库的url -->
<property name="url" value="jdbc:mysql://localhost:3306/wz?useSSL=false&amp;serverTimezone=UTC" />
<!--连接数据库的用户名 -->
<property name="username" value="root" />
<!--连接数据库的密码 -->
<property name="password" value="jin200727" />
</bean>

<!--配置JDBC模板 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<!--默认必须使用数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>

<!-- 配置事务管理-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--配置dao层接口-->
<bean id="userdao" class="Tx.dao.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
<property name="transactionManager" ref="transactionManager" />
</bean>

</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
34
@Repository
public class BookDao {

@Autowired
JdbcTemplate jdbcTemplate;

/**
* 1、减余额
*
* 减去某个用户的余额
*/
public void updateBalance(String userName,int price){
String sql = "UPDATE account SET balance=balance-? WHERE username=?";
jdbcTemplate.update(sql, price,userName);
}

/**
* 2、按照图书的ISBN获取某本图书的价格
* @return
*/
public int getPrice(String isbn){
String sql = "SELECT price FROM book WHERE isbn=?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
}

/**
* 3、减库存;减去某本书的库存;为了简单期间每次减一
*/
public void updateStock(String isbn){
String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
jdbcTemplate.update(sql, isbn);
}


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
package Tx2.service;

import Tx2.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;



@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账;传入哪个用户买了哪本书
* @param username
* @param isbn
*/
@Transactional
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);

int price = bookDao.getPrice(isbn);
//2、减余额
bookDao.updateBalance(username, price);
int i =10/0;
}
}

事务的传播行为:

但事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:饭阀可能继续在现在的事务中运行,也可能开启一个新的事务,并在自己的事务之中运行,事务的传播可以由传播属性指定。spring定义了7种传播的行为。(如果多个事务进行嵌套运行,子事务是否要和大事务共用一个事务)

image-20220122220832282

传播属性

img

required 看上级事务是相同吗,事务的属性都是继承大事务的,

image-20220122223509995

image-20220122223234707

​ 事务传播属性可以在@Transactional注解的propagation属性中定义。

image-20220121173532466

4、timeout:超时时间

(1)事务需要在一定时间内进行提交,如果不提交进行回滚

(2)默认值是 -1 ,设置时间以秒单位进行计算

5、readOnly:是否只读

(1)读:查询操作,写:添加修改删除操作

(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作

(3)设置 readOnly 值是 true,设置成 true 之后,只能查询 6、rollbackFor:回滚

(1)设置出现哪些异常进行事务回滚

7、noRollbackFor:不回滚

(1)设置出现哪些异常不进行事务回滚

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
package Tx2.service;

import Tx2.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;



@Service
public class BookService {

@Autowired
BookDao bookDao;

/**
* 结账;传入哪个用户买了哪本书
* @param username
* @param isbn
* 只要在该方法中使用了@Transactional,只要出现了异常就会都不进行执行
* 事务细节
*
* isolation :事务的隔离级别
* propagation = 事务的隔离级别
* noRollbackFor-Class,哪些异常不需要回滚
* rollbackForClassName
* readOnly 设置事务为只读属性
* 能够加快查询的速度,不用管事务的操作
*
* noRollbackFor =
* timeout-int 超时 : 事务超出指定市场后自动终止并防止回滚
*异常分类:
* 运行时异常: 可以不用处理
* 编译异常: 必须要进行处理
* 默认回滚;默认发生运行时异常都会回滚,发生编译时异常不会回滚
* * noRollbackFor-Class,哪些异常不需要回滚
* * rollbackForClassName
*/
@Transactional(timeout = 3)
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int price = bookDao.getPrice(isbn);
//2、减余额
bookDao.updateBalance(username, price);
int i =10/0;
}
}

事务的隔离级别

数据库事务并发问题

假设现在有两个事务:Transaction01和Transaction02并发执行。

脏读:一个未提交事务读取到另一个未提交事务的数据

​ [1]Transaction01将某条记录的AGE值从20修改为30。

​ [2]Transaction02读取了Transaction01更新后的值:30。

​ [3]Transaction01回滚,AGE值恢复到了20。

​ [4]Transaction02读取到的30就是一个无效的值。

不可重复读:一个未提交事务读取到另一提交事务修改数据

​ [1]Transaction01读取了AGE值为20。

​ [2]Transaction02将AGE值修改为30。

​ [3]Transaction01再次读取AGE值为30,和第一次读取不一致。

幻读::一个未提交事务读取到另一提交事务添加数据

​ [1]Transaction01读取了STUDENT表中的一部分数据。

​ [2]Transaction02向STUDENT表中插入了新的行。

​ [3]Transaction01读取了STUDENT表时,多出了一些行。

通过设置事务的隔离级别,解决读的问题:

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

读未提交:READ UNCOMMITTED

允许Transaction01读取Transaction02未提交的修改。

读已提交:READ COMMITTED

​ 要求Transaction01只能读取Transaction02已提交的修改。

③可重复读:REPEATABLE READ

​ 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

串行化:SERIALIZABLE

​ 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

⑤各个隔离级别解决并发问题的能力见下表

脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

⑥各种数据库产品对事务隔离级别的支持程度

Oracle MySQL
READ UNCOMMITTED ×
READ COMMITTED
REPEATABLE READ × √(默认)
SERIALIZABLE

image-20220122192401725

image-20220122192733815

image-20220122192803281

同一事务之下避免了脏读,就是不会读取到已修改未提交的数据,但是没有避免同一事务不可重复度的问题,就是同一个事务下,读到不同的数据

image-20220122192816781

基于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
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:tx.properties"/>
<!-- 开启注解扫描 -->
<context:component-scan base-package="Tx2.*"></context:component-scan>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
<!--配置JDBC模板 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<!--默认必须使用数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>

<!-- 配置事务管理-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务,那个方法需要添加-->
<tx:method name="checkout" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* Tx2.*(..))"/>
<!--配置切面 事务建议:事务增强 ,advice-ref : 指向事务管理器的配置-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
</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
34
35
36
37
38
39
40
41
42
@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到 ioc 容器中根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入 dataSource
jdbcTemplate.setDataSource(dataSource)
@Configuration //配置类
@ComponentScan(basePackages = "com.atguigu") //组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//创建 JdbcTemplate 对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到 ioc 容器中根据类型找到 dataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入 dataSource
jdbcTemplate.setDataSource(dataSource)