Sprint Boot
Spring
第一章 Spring 概述
1.1. 什么是spring
spring就是一个java框架,使用java语言开发的, 轻量级的, 开源的框架。 可以在j2se、j2ee项目中都可以使用。
spring核心技术: ioc, aop
spring又叫做:容器, spring作为容器, 装的是java对象。 可以让spring创建java对象, 给属性赋值。
spring作用: 实现解耦合, 解决java对象之间的耦合, 解决模块之间的耦合。
tomcat也是容器:管理的是servlet, listener, filter等对象。
创建HelloServlet 类,写web.xml
spring:创建SomeServiceImpl, 写spring的配置文件
1.2 spring的地址
https://spring.io
1.3 Spring优点
Spring 是一个框架,是一个半成品的软件。有 20 个模块组成。它是一个容器管理对象,容器是装东西的,Spring 容器不装文本,数字。装的是对象。Spring 是存储对象的容器。 (1) 轻量 Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring核心功能的所需的 jar 总共在 3M 左右。 Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar
(2) 针对接口编程,解耦合 Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。
(3) AOP 编程的支持 通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
(4) 方便集成各种优秀框架 Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架(如 Struts,Hibernate、MyBatis)等的直接支持。简化框架的使用。Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。
第二章 IoC 控制反转
2.1 IoC 概念
IoC,Inversion of Control : 控制反转, 是一个理论,一个指导思想。 指导开发人员如何使用对象,管理对象的。 把对象的创建,属性赋值,对象的声明周期都交给代码之外的容器管理。
1) IoC分为 控制和反转
- 控制:对象创建,属性赋值, 对象声明周期管理
- 反转:把开发人员管理对象的权限转移给了代码之外的容器实现。 由容器完成对象的管理
- 正转:开发人员在代码中, 使用 new 构造方法创建对象。 开发人员掌握了对象的创建,属性赋值,对象从开始到销毁的全部过程。 开发人员有对 对象 全部控制。
通过容器,可以使用容器中的对象(容器已经创建了对象, 对象属性赋值了, 对象也组装好了)。Spring就是一个容器,可以管理对象,创建对象,给属性赋值。
2) IoC的技术实现
DI ( 依赖注入) :Dependency Injection, 缩写是DI . 是IoC的一种技术实现。 程序只需要提供要使用的对象的名称就可以了, 对象如何创建, 如何从容器中查找,获取都由容器内部自己实现。
依赖名词: 比如说ClassA类使用了ClassB的属性或者方法,叫做ClassA依赖ClassB.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ClassB{
public void createOrder(){}
}
public class ClassA{
//属性
private ClassB b = new ClassB();
public void buy(){
b.createOrder();
}
}
执行ClassA的buy()
ClassA a = new ClassA();
a.buy();
3) Spring框架使用的DI实现IoC.
通过spring框架, 只需要提供要使用的对象名词就可以了。 从容器中获取名称对应的对象。 spring底层使用的 反射机制, 通过反射创建对象,给属性。
2.2 Spring的配置文件
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
spring标准的配置文件:
1)根标签是 beans
2) beans 后面的是约束文件说明
3)beans里面是bean声明。
4)什么是bean: bean就是java对象, spring容器管理的java对象,叫做bean
2.3 spring容器创建对象的特点
容器对象ApplicationContext:接口
通过ApplicationContext对象,获取要使用的 其他java对象, 执行getBean(“
的id”) spring默认是调用类的无参数构造方法,创建对象
spring读取配置文件, 一次创建好所有的java对象, 都放到map中。
2.4 DI:给属性赋值。
spring调用类的无参数构造方法,创建对象。 对象创建后给属性赋值。
给属性赋值可以使用
1)xml配置文件中的标签和属性;
2)使用注解。
DI分类:
- set注入,也叫做设值注入
- 构造注入。
2.4.1 基于xml的DI
在xml配置文件中使用标签和属性,完成对象创建,属性赋值。
1) set注入,也叫做设值注入。
概念: spring调用类中的set方法,在set方法中可以完成属性赋值。 推荐使用。
1
2
3
4
5
简单类型的设值注入,使用value
<bean id="mySchool" class="com.bjpowernode.ba02.School">
<property name="name" value="北京大学"/>
<property name="address" value="北京的海淀区"/>
</bean>
1
2
3
4
5
6
7
<!--引用类型set注入-->
<bean id="myStudent" class="com.bjpowernode.ba02.Student">
<property name="name" value="李四"/>
<property name="age" value="22" />
<!--引用类型的赋值-->
<property name="school" ref="mySchool" /><!--setSchool(mySchool)-->
</bean>
2) 构造注入
构造注入:spring调用类中的有参数构造方法, 在创建对象的同时,给属性赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--构造注入,使用name属性-->
<bean id="myStudent" class="com.bjpowernode.ba03.Student">
<constructor-arg name="myage" value="22" />
<constructor-arg name="myname" value="李四"/>
<constructor-arg name="mySchool" ref="mySchool"/>
</bean>
<!--构造注入,使用index,参数的位置,构造方法参数从左往右位置是0,1,2-->
<bean id="myStudent2" class="com.bjpowernode.ba03.Student">
<constructor-arg index="1" value="28"/>
<constructor-arg index="0" value="张三"/>
<constructor-arg index="2" ref="mySchool" />
</bean>
<!--构造注入,省略index属性-->
<bean id="myStudent3" class="com.bjpowernode.ba03.Student">
<constructor-arg value="张峰"/>
<constructor-arg value="28"/>
<constructor-arg ref="mySchool" />
</bean>
3)引用类型的自动注入
概念: spring可以根据某些规则给引用类型完成赋值。 只对引用类型有效。 规则byName, byType.
①:byName(按名称注入): java类中引用类型属性名称和spring容器中bean的id名称一样的,且数据类型也是一样的,这些bean能够赋值给引用类型。
1
2
3
4
5
6
7
<!-- byName 自动注入 -->
<bean id="myStudent" class="com.bjpowernode.ba04.Student" autowire="byName">
<property name="name" value="李四"/>
<property name="age" value="22" />
<!--引用类型的赋值-->
<!--<property name="school" ref="mySchool" />-->
</bean>
②:byType(按类型注入):java类中引用类型的数据类型和spring容器中bean的class值是同源关系的,这样的bean赋值给引用类型。
1
注意: 在xml配置文件中, 符合条件的对象,只能有一个。 多余一个是报错的。
1
2
3
4
5
6
7
<!-- byType 自动注入 -->
<bean id="myStudent" class="com.bjpowernode.ba05.Student" autowire="byType">
<property name="name" value="张三"/>
<property name="age" value="26" />
<!--引用类型的赋值-->
<!--<property name="school" ref="mySchool" />-->
</bean>
4)作业 IoC
需求:模拟一用户注册操作。
需要定义一个dao接口(UserDao)接口中的方法 insertUser(SysUser user ) , 定义接口的实现类 MySqlUserDao
inserUser()方法里面不需要操作数据, 输出“使用了dao执行insert操作”。
需要定义一个service接口(UserService),定义接口的实现类 UserServiceImpl. 在service的实现类有一个 UserDao类型的属性。 service类中有一个方法 addUser(SysUser user).
操作是 service类中的addUser() { userDao.insertUser() } 完成注册。
定义一个实体类 SysUser, 表示用户的数据。
要求实现:
程序中的UserServiceImpl, MySqlUserDao这些类都有spring容器创建和管理, 同时要给 UserServiceImpl类中的 userDao属性赋值。 从spring容器中获取 UserServiceImpl类型的对象, 调用addUser()方法,输出“使用了dao执行insert操作”
5) 项目中使用多个spring配置文件
分多个配置文件的方式:
1)按功能模块分,一个模块一个配置文件。
2)按类的功能分,数据库操作相关的类在一个文件, service类在一个配置文件, 配置redis,事务等等的一个配置文件。
spring管理多个配置文件: 常用的是包含关系的配置文件。 项目中有一个总的文件, 里面是有impor标签包含其他的多个配置文件。
语法:
1
2
3
4
5
6
总的文件(xml)
<import resource="其他的文件的路径1"/>
<import resource="其他的文件的路径2"/>
关键字“classpath:”:表示类路径, 也就是类文件(class文件)所在的目录。 spring到类路径中加载文件
什么时候使用classpath: 在一个文件中要使用其他的文件, 需要使用classpath
2.4.2 基于注解的DI
基于注解的DI:使用spring提供的注解,完成java对象创建,属性赋值。
注解使用的核心步骤:
1.在源代码加入注解,例如@Component
2.在spring的配置文件,加入组件扫描器的标签
1
<context:component-scan base-package="注解所在的包名"/>
扫描多个包的三种方式
使用多次组件扫描器
使用分隔符(;或,),指定多个包
1
<context:component-scan base-package="a;b,c" />
指定父包
1.创建对象的注解
@Component
普通java对象@Repository
dao对象,持久层对象, 表示对象能访问数据库。@Service
service对象,业务层对象,处理业务逻辑,具有事务能力@Controller
控制器对象,接收请求,显示请求的处理结果。 视图层对象
2.简单类型属性赋值
@Value
: 1)在属性定义的上面,无需set方法,推荐使用
2)在set方法的上面
来源于外部文件@Value("${name}")
1
<context:property-placeholder location="classpath:/myconf.properties" />
3.引用类型赋值
@Autowired: spring提供的注解 . 支持byName, byType
- 位置:1)属性上面 2)set上面
- @Autowired: 默认就是byType
- @Autowired @Qualifier : 使用byName
- Required属性:true没有会报错,false可以是null
@Resource : 来自jdk中的注解,给引用类型赋值的,默认是byName
@Resource: 先使用byName, 在byType
@Resource(name=”bean的名称”) :只使用byName注入
2.5 IoC 总结
IoC:管理对象的,把对象放在容器中,创建,赋值,管理依赖关系。
IoC:通过管理对象,实现解耦合。 IoC解决处理业务逻辑对象之间的耦合关系, 也就是service和dao之间的解耦合。
spring作为容器适合管理什么对象 ?
1)service对象 ,dao对象。
2)工具类对象。
不适合交给spring的对象 ?
1)实体类。
2)servlet , listener ,filter等web中的对象。他们是tomcat创建和管理的。
第三章 AOP 面向切面编程
3.1 增加功能,导致的问题
在源代码中, 业务方法中增加的功能。
1)源代码可能改动的比较多。
2)重复代码比较多。
3)代码难于维护。
3.2 AOP 概念
3.1 什么是AOP
AOP(Aspect Orient Programming) : 面向切面编程
Aspect :(业务无关)表示切面, 给业务方法增加的功能,叫做切面。 切面一般都是非业务功能, 而且切面功能一般都是可以复用的。 例如 日志功能, 事务功能, 权限检查,参数检查, 统计信息等等。
Orient:面向,对着
Programming:编程。
怎么理解面向切面编程 ? 以切面为核心设计开发你的应用。
1)设计项目时, 找出切面的功能。
2)安排切面的执行时间, 执行的位置。
3.2 AOP作用
1)让切面功能复用
2)让开发人员专注业务逻辑。 提高开发效率
3)实现业务功能和其他非业务功能解耦合。
4)给存在的业务方法, 增加功能,不用修改原来的代码
3.3 AOP中术语
1)Aspect:切面, 给业务方法增加的功能。
2)JoinPoint:连接点, 连接切面的业务方法。 在这个业务方法执行时,会同时执行切面的功能。
3)Pointcut: 切入点, 是一个或多个连接点集合。 表示这些方法执行时,都能增加切面的功能。表示切面执行的位置。
4)target: 目标对象, 给那个对象增加切面的功能, 这个对象就是目标对象。
5)Advice:通知(增强),表示切面的执行时间。 在目标方法之前执行切面,还是目标方法之后执行切面。
AOP中重要的三个要素: Aspect, Pointcut , Advice. 这个概念的理解是: 在Advice的时间,在Pointcut的位置, 执行Aspect
AOP是一个动态的思想。 在程序运行期间,创建代理(ServcieProxy),使用代理执行方法时,增加切面的功能。这个代理对象是存在内存中的。
3.4 什么时候你想用AOP
你要给某些方法 增加相同的一些功能。 源代码不能改。 给业务方法增加非业务功能,也可以使用AOP
3.5 AOP技术思想的实现
使用框架实现AOP。 实现AOP的框架有很多。 有名的两个
1) Spring : Spring框架实现AOP思想中的部分功能。 Spring框架实现AOP的操作比较繁琐,比重。
2) Aspectj : 独立的框架,专门是AOP。 属于Eclipse
3.6 使用AspectJ框架实现AOP
AspectJ框架可以使用注解和xml配置文件两种方式 实现 AOP
3.6.1 通知
Aspectj表示切面执行时间,用的通知(Advice)。 这个通知可以使用注解表示。
讲5个注解, 表示切面的5个执行时间, 这些注解叫做通知注解。
@Before : 前置通知
@AfterRetunring: 后置通知
@Around: 环绕通知
@AfterThrowing:异常通知
@After:最终通知
3.6.2. Pointcut 位置
Pointcut 用来表示切面执行的位置, 使用Aspectj中切入点表达式。
切入点表达式语法: execution(访问权限 ==方法返回值 方法声明(参数)== 异常类型)
3.6.3 @Before前置通知
要求:
- 方法 public void 方法名称自定义
- 方法可以有参数,如果有时
JoinPoint
类型,也可以没有
属性:value 切入点表达式,表示切面的执行位置,方法和切面同时执行
特点:
- 执行时间:目标方法前先执行
- 不会影响目标方法的执行
- 不会修改目标方法的内容
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
/**
* 切面类中的通知方法,可以有参数
* JoinPoint必须是他。
*
* JoinPoint: 表示正在执行的业务方法。 相当于反射中 Method
* 使用要求:必须是参数列表的第一个
* 作用:获取方法执行时的信息,例如方法名称, 方法的参数集合
*/
@Before(value = "execution(* *..SomeServiceImpl.do*(..) )")
public void myBefore2(JoinPoint jp){
//获取方法的定义
System.out.println("前置通知中,获取目标方法的定义:"+jp.getSignature());
System.out.println("前置通知中,获取方法名称="+jp.getSignature().getName());
//获取方法执行时参数
Object args []= jp.getArgs();// 数组中存放的是 方法的所有参数
for(Object obj:args){
System.out.println("前置通知,获取方法的参数:"+obj);
}
String methodName = jp.getSignature().getName();
if("doSome".equals(methodName)){
//切面的代码。
System.out.println("doSome输出日志=====在目标方法之前先执行==:"+ new Date());
} else if("doOther".equals(methodName)){
System.out.println("doOther前置通知,作为方法名称,参数的记录。");
}
}
JoinPoint: 表示正在执行的业务方法,相当于反射中的 Method
- 使用要求: 必须是参数列表的第一个
- 作用:获取方法执行时的信息,例如方法的名称,方法的参数集合
3.6.4 @AfterReturning 后置通知
@AfterReturning: 在目标方法之后执行的
属性:
value 切入点表达式
returning 自定义的变量,表示目标方法的返回值的
- 自定义变量名称必须和通知方法的形参名一样
特点:
- 1.在目标方之后,执行的。
- 2.能获取到目标方法的执行结果。
- 3.不会影响目标方法的执行
参数:
- Object res: 表示目标方法的返回值,使用res接收doOther的调用结果。
- Object res= doOther();
后置通知的执行顺序
Object res = SomeServiceImpl.doOther(..);
myAfterReturning(res);
思考:
- doOther方法返回是String ,Integer ,Long等基本类型。在后置通知中,修改返回值, 是不会影响目标方法的最后调用结果的
- doOther返回的结果是对象类型,例如Student。在后置通知方法中,修改这个Student对象的属性值,会影响最后调用结果
- 想用 JoinPoint 一定要放在第一个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
returning = "res")
public void myAfterReturning(JoinPoint jp, Object res){
//修改目标方法的返回值
if(res != null){
res = "HELLO Aspectj";
}
System.out.println("后置通知,在目标方法之后,执行的。能拿到执行结果:"+res);
//Object res有什么用
if("abcd".equals(res)){
System.out.println("根据返回值的不同,做不同的增强功能");
} else if("add".equals(res)){
System.out.println("doOther做了添加数据库, 我做了备份数据");
}
}
3.6.5 ==@Around== 环绕通知
@Around(value=”切入点表达式”)
使用环绕通知: 就是调用 切面类中的通知方法。
@Around:环绕通知
- 属性:value 切入点表达式
- 位置:在方法定义的上面
返回值:Object ,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
参数: ProceedingJoinPoint, 相当于反射中 Method。
- 作用:执行目标方法的,等于Method.invoke()
- public interface ProceedingJoinPoint extends JoinPoint {}
特点:
- 1.在目标方法的前和后都能增强功能
- 2.控制目标方法是否执行
- 3.修改目标方法的执行结果。
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
@Around("execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//获取方法执行时的参数值
String name = "";
Object args [] = pjp.getArgs();
if( args != null && args.length > 0){
Object arg = args[0];
if(arg !=null){
name=(String)arg;
}
}
Object methodReturn = null;
System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=="+ new Date());
//执行目标方法 ProceedingJoinPoint,表示doFirst
if("lisi".equals(name)){
methodReturn = pjp.proceed();//method.invoke(),表示执行doFirst()方法本身
}
if( methodReturn != null){
methodReturn ="环绕通知中,修改目标方法原来的执行结果";
}
System.out.println("环绕通知,在目标方法之后,增加了事务提交功能");
//return "HelloAround,不是目标方法的执行结果";
//返回目标方法执行结果。没有修改的。
return methodReturn;
}
3.6.6 @AfterThrowing 异常通知
语法@AfterThrowing(value=”切入点表达式”,throwing=”自定义变量”)
- 属性: value 切入点表达式
- throwing 自定义变量,表示目标方法抛出的异常。
- 变量名必须和通知方法的形参名一样
- 位置:在方法的上面
特点:
- 在目标方法抛出异常后执行的, 没有异常不执行
- 能获取到目标方法的异常信息。
- 不是异常处理程序。可以得到发生异常的通知, 可以发送邮件,短信通知开发人员。看做是目标方法的监控程序。
异常通知的执行
1
2
3
4
5
try{
SomeServiceImpl.doSecond(..)
}catch(Exceptoin e){
myAfterThrowing(e);
}
1
2
3
4
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:"+ex.getMessage());
}
异常发生可以做: 1.记录异常的时间,位置,等信息。 2.发送邮件,短信,通知开发人员
3.6.7 @After 最终通知
语法: @After(value=”切入点表达式”)
@After:最终通知 * 属性: value 切入点表达式
- 位置: 在方法的上面
特点:
在目标方法之后执行的。
总是会被执行。
可以用来做程序最后的收尾工作。例如清除临时数据,变量。 清理内存 最终通知
1
2
3
4
5
try{
SomeServiceImpl.doThird(..)
}finally{
myAfter()
}
1
2
3
4
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
3.6.8 @Pointcut定义和管理切入点注解
@Pointcut(value=”切入点表达式”)
属性: value 切入点表达式
位置: 在一个自定义方法的上面, 这个方法看做是切入点表达式的别名。
其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式了
1
2
3
4
5
6
7
8
9
@After(value = "mypt()")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
@Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
private void mypt(){
//无需代码
}
3.7 AOP总结
AOP是一种动态的技术思想, 目的是实现业务功能和非业务功能的解耦合。 业务功能是独立的模块, 其他功能也是独立的模块。例如事务功能,日志等等。 让这些事务,日志功能是可以被复用的。
当目标方法需要一些功能时,可以在不修改,不能修改源代码的情况下, 使用aop技术在程序执行期间,生成代理对象, 通过代理执行业务方法,同时增加功能。
第四章 Spring集成MyBatis
4.1 集成思路
spring能集成很多的框架,是spring一个优势功能。 通过集成功能,让开发人员使用其他框架更方便。
集成使用的是spring ioc 核心技术。
4.2 要使用框架,例如mybatis,怎么使用mybatis ?
使用mybatis,需要创mybatis框架中的某些对象, 使用这些对象,就能使用mybatis提供的功能了。
分析: mybatis执行sql语句,需要使用那些对象
需要有Dao接口的代理对象, 例如StudentDao接口, 需要一个它的代理对象
使用 SqlSession.getMapper(StudentDao.class),得到dao代理对象
需要有SqlSessionFactory, 创建SqlSessionFactory对象,才能使用openSession()得到SqlSession对象
数据源DataSource对象, 使用一个更强大,功能更多的连接池对象代替mybatis自己的PooledDataSource
mappers 中使用 package 的要求
- mapper文件和dao接口在同一目录
- mapper和dao接口名称一致
第五章 Spring 事务
5.1 事务的概念
什么事务? 事务是一些sql序列的集合, 是多条sql, 作为一个整体执行。
1
2
3
4
5
6
mysql执行事务
beginTransaction 开启事务
insert into student() values.....
select * from student where id=1001
update school set name=xxx where id=1005
endTransaction 事务结束
什么情况下需要使用事务?
一个操作需要多条(2条或2条以上的sql)sql语句一起完成, 操作才能成功。
5.2 在程序中事务在哪说明
事务:加在业务类的方法上面(public方法上面),表示业务方法执行时,需要事务的支持。
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
public class AccountService{
private AccountDao dao;
private MoneyDao dao2;
// 在service(业务类)的public方法上面,需要说明事务。
public void trans(String a, String b, Integer money){
dao.updateA();
dao.updateB();
dao2.insertA();
}
}
public class AccountDao{
public void updateA(){}
public void updateB(){}
}
public class MoneyDao{
public void insertA(){}
public void deleteB(){}
}
5.3 事务管理器
5.3.1 不同的数据库访问技术,处理事务是不同的
1)使用jdbc访问数据库, 事务处理。
1
2
3
4
5
6
7
8
public void updateAccount(){
Connection conn = ...
conn.setAutoCommit(false);
stat.insert()
stat.update();
conn.commit();
con.setAutoCommit(true)
}
2) mybatis执行数据库,处理事务
1
2
3
4
5
6
7
8
9
10
public void updateAccount(){
SqlSession session = SqlSession.openSession(false);
try{
session.insert("insert into student...");
session.update("update school ...");
session.commit();
}catch(Exception e){
session.rollback();
}
}
5.3. 2 spring统一管理事务, 把不同的数据库访问技术的事务处理统一起来。
使用spring的事务管理器,管理不同数据库访问技术的事务处理。 开发人员只需要掌握spring的事务处理一个方案, 就可以实现使用不同数据库访问技术的事务管理。
管理事务面向的是spring, 有spring管理事务,做事务提交,事务回顾。
5.3.3 Spring事务管理器
Spring框架使用事务管理器对象,管理所有的事务。
事务管理器接口: PlatformTransactionManager
作用: 定义了事务的操作, 主要是commit(), rollback()
事务管理器有很多实现类: 一种数据库的访问技术有一个实现类。 由实现类具体完成事务的提交,回顾。
意味着: jdbc或者mybatis访问数据库有自己的事务管理器实现类 : DataSourceTranactionManager
hibernate框架,他的事务管理器实现类: HibernateTransactionManager.
事务管理器工作方式:
5.3.4 事务的提交和回顾的时机
什么时候提交事务,回滚事务 ?
当你的业务方法正常执行时,没有异常,事务是提交的。 如果你的业务方法抛出了运行时异常, 事务是回滚的。
异常分类:
Error: 严重错误。 回滚事务。
Exception : 异常类,可以出来的异常情况
1) 运行时异常: RuntimeException和他的子类都是运行时异常, 在程序执行过程中抛出的异常。 常见的运行时异常: NullPoinerException , NumberFormatException , ArithmeticException, IndexOutOfBoundsException.
2) 受查异常 : 编写java代码的时候,必须出来的异常。 例如IOException , SQLException , FileNotFoundException
怎么记忆:
方法中抛出了运行时异常, 事务回滚,其他情况(正常执行方法,受查异常)就是提交事务。
5.3.5 事务使用的AOP的环绕通知
环绕通知:可以在目标方法的前和后都能增强功能,不需要修改代码代码
1
2
3
4
5
6
7
8
9
10
11
12
spring给业务方法在执行时,增加上事务的切面功能
@Around("execution(* 所有的业务类中的方法)")
public Object myAround(ProceedingJoinPoint pjp) {
try{
PlatformTransactionManager.beginTransaction();//使用spring的事务管理器,开启事务
pjp.proceed(); //执行目标方法 //doSome()
PlatformTransactionManager.commit();//业务方法正常执行,提交事务
}catch(Exception e){
PlatformTransactionManager.rollback();//业务方法正常执行,回滚事务
}
}
5.4 事务定义接口TransactionDefinition
TransactionDefinition接口。定义了三类常量, 定义了有关事务控制的属性。
事务的属性:1) 隔离级别 2)传播行为 3)事务的超时
给业务方法说明事务属性。和ACID不一样。
5.4.1 隔离级别
隔离级别:控制事务之间影响的程度。
5个值,只有四个隔离级别
1)DEFAULT : 采 用 DB 默 认 的 事 务 隔 离 级 别 。 MySql 的 默 认 为 REPEATABLE_READ;
Oracle 默认为 READ_COMMITTED。
2)READ_UNCOMMITTED:读未提交。未解决任何并发问题。 3)READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。 4)REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 5)SERIALIZABLE:串行化。不存在并发问题。
5.4.2 超时时间
超时时间,以秒为单位 。 整数值。 默认是 -1
超时时间:表示一个业务方法最长的执行时间,没有到达时间没有执行完毕,spring回滚事务。
5.4.3 传播行为
传播行为有7个值。
传播行为:业务方法在调用时,事务在方法之间的,传递和使用。
使用传播行为,标识方法有无事务。
PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW PROPAGATION_SUPPORTS
以上三个需要掌握的。
PROPAGATION_MANDATORY PROPAGATION_NESTED PROPAGATION_NEVER PROPAGATION_NOT_SUPPORTED
1) REQUIRED: spring默认传播行为, 方法在调用的时候,如果存在事务就是使用当前的事务,如果没有事务,则新建事务, 方法在新事务中执行。
2) SUPPORTS:支持, 方法有事务可以正常执行,没有事务也可以正常执行。
3)REQUIRES_NEW:方法需要一个新事务。 如果调用方法时,存在一个事务,则原来的事务暂停。 直到新事务执行完毕。 如果方法调用时,没有事务,则新建一个事务,在新事务执行代码。
5.5 Spring框架使用自己的注解@Transactional控制事务
@Transactional注解, 使用注解的属性控制事务(隔离级别,传播行为,超时)
属性:
- propagation : 事务的传播行为, 他使用的 Propagation类的枚举值。例如 Propagation.REQUIRED
- isolation : 表示隔离级别, 使用Isolation类的枚举值,表示隔离级别。 默认 Isolation.DEFAULT
- readOnly: boolean类型的值,表示数据库操作是不是只读的。默认是false
- timeout: 事务超时,默认是-1, 整数值,单位是秒。 例如 timeout=20
- rollbackFor:表示回滚的异常类列表, 他的值是一个数组,每个值是异常类型的class。
- rollbackForClassName:表示回滚的异常类列表,他的值是异常类名称,是String类型的值
- noRollbackFor: 不需要回滚的异常类列表。 是class类型的。
- noRollbackForClassName: 不需要回滚的异常类列表,是String类型的值
位置:
1)在业务方法的上面, 是在public方法的上面
2) 在类的上面。
注解的使用步骤:
- 在spring的配置文件,声明事务的内容
- 声明事务管理器,说明使用哪个事务管理器对象
- 声明使用注解管理事务, 开启是注解驱动
2)在类的源代码中,加入@Transactional.
事务的控制模式: 1. 编程式,在代码中编程控制事务。 2. 声明式事务。不用编码
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring配置文件
<!--声明事务的控制-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源DataSource-->
<property name="dataSource" ref="myDataSource" />
</bean>
<!--开启事务注解驱动: 告诉框架使用注解管理事务
transaction-manager:指定事务管理器的id
-->
<tx:annotation-driven transaction-manager="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
25
26
27
28
29
30
31
32
33
34
35
36
java代码
//@Transactional 放在public方法的上面。表示方法有事务功能
/*
第一种设置方式
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false, timeout = 20,
rollbackFor = {NullPointerException.class,NotEnougthException.class})
*/
/*
第二种设置方式
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false, timeout = 20
)
解释 rollbackFor 的使用;
1)框架首先检查方法抛出的异常是不是在 rollbackFor 的数组中, 如果在一定回滚。
2)如果方法抛出的异步不在 rollbackFor 数组, 框架会继续检查 抛出的异常 是不是 RuntimeException.
如果是RuntimeException, 一定回滚。
例如 抛出 SQLException , IOException
rollbackFor={SQLException.class, IOException.class}
*/
//第三种方式: 使用默认值 REQUIRED , 发生运行时异常回滚。
@Transactional
@Override
public void buy(Integer goodsId, Integer num) { }
@Transactional使用的特点:
1.spring框架自己提供的事务控制
2.适合中小型项目。
3.使用方便,效率高。
5.6 使用Aspectj框架在spring的配置文件中,声明事务控制
使用aspectj的aop,声明事务控制叫做声明式事务
使用步骤:
pom.xml加入 spring-aspects的依赖
在spring的配置文件声明事务的内容
1)声明事务管理器
2)声明业务方法需要的事务属性
3)声明切入点表达式
声明式事务
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
<!--声明式事务: 不用写代码 -->
<!--1. 声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource" />
</bean>
<!--2.声明业务方法的事务属性(隔离级别,传播行为,超时)
id:给业务方法配置事务段代码起个名称,唯一值
transaction-manager:事务管理器的id
-->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
<!--给具体的业务方法增加事务的说明-->
<tx:attributes>
<!--
给具体的业务方法,说明他需要的事务属性
name: 业务方法名称。
配置name的值: 1. 业务方法的名称; 2. 带有部分通配符(*)的方法名称; 3 使用*
propagation:指定传播行为的值
isolation:隔离级别
read-only:是否只读,默认是false
timeout:超时时间
rollback-for:指定回滚的异常类列表,使用的异常全限定名称
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
read-only="false" timeout="20"
rollback-for="java.lang.NullPointerException,com.bjpowernode.excetion.NotEnougthException"/>
<!--在业务方法有命名规则后, 可以对一些方法使用事务-->
<tx:method name="add*" propagation="REQUIRES_NEW"
rollback-for="java.lang.Exception" />
<tx:method name="modify*"
propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="remove*"
propagation="REQUIRED" rollback-for="java.lang.Exception" />
<!--以上方法以外的 * :querySale, findSale, searchSale -->
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!--声明切入点表达式: 表示那些包中的类,类中的方法参与事务-->
<aop:config>
<!--声明切入点表达式
expression:切入点表达式, 表示那些类和类中的方法要参与事务
id:切入点表达式的名称,唯一值
expression怎么写?
-->
<aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))" />
<!--关联切入点表达式和事务通知-->
<aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePointcut" />
</aop:config>
声明式事务优缺点:
缺点: 理解难, 配置复杂。
优点: 代码和事务配置是分开的。 控制事务源代码不用修改。
能快速的了解和掌控项目的全部事务。 适合大型项目。
第六章 Spring和Web
6.1 现在使用容器对象的问题
- 创建容器对象次数多
- 在多个servlet中,分别创建容器对象
6.2 需要一个什么样的容器对象
- 容器对象只有一个, 创建一次就可以了
- 容器对象应该在整个项目中共享使用。 多个servlet都能使用同一个容器对象
解决问题使用监听器 ServletContextListener (两个方法 初始时执行的,销毁时执行的)
在监听器中,创建好的容器对象,应该放在web应用中的ServletContext作用域中。
6.3 ContextLoaderListener
ContextLoaderListener 是一个监听器对象, 是spring框架提供的, 使用这个监听器作用:
1.创建容器对象,一次
2.把容器对象放入到ServletContext作用域。
使用步骤:
1.pom.xml加入依赖spring-web
2.web.xml 声明监听器
依赖
1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
监听器的设置
1
2
3
4
5
6
7
8
9
10
11
<context-param>
<!--
contextConfigLocation:名称是固定的, 表示自定义spring配置文件的路径
-->
<param-name>contextConfigLocation</param-name>
<!--自定义配置文件的路径-->
<param-value>classpath:spring-beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
6.4 ContextLoaderListener 源代码
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
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
//监听器的初始方法
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
}
private WebApplicationContext context;
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
try {
if (this.context == null) {
//创建srping的容器对象
this.context = this.createWebApplicationContext(servletContext);
}
//把容器对象放入的ServletContext作用域
//key=WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
//value=容器对象
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
} catch (Error | RuntimeException var8) {
}
}
}
//WebApplicationContext是web项目中使用的容器对象
public interface WebApplicationContext extends ApplicationContext