一、Spring概述
1、Spring框架
Spring就是一个java框架,使用java语言开发,轻量级的开源框架,可以在j2se,j2ee都可使用。
Spring核心技术:IOC,AOP,核心是控制反转(IOC)和面向切面编程(AOP)
Spring又叫做:容器,Spring 作为容器,装的是java对象,可以让 Spring 创建java对象,给属性赋值
Spring作用:实现结耦合,解决java对象之间的耦合,解决模块之间的耦合
tomcat:也是容器,管理的是servlet,listener,filter等对象,创建HelloServlet,写web.xml
Spring:创建SomeServiceImpl,写 Spring 配置文件
2、Spring的优点
Spring 是一个框架,是一个半成品的软件,有20个模块组成,它是一个容器管理对象,容器是装东西的,Spring 容器不装文本、数字,装的是对象。 Spring 是存储对象的容器。
(1)轻量
Spring 框架使用的 jar 都比较小,一般在1M以下,Spring 核心功能的所需的 jar 总共在3M左右。
Spring 框架运行占用的资源少,运行效率高,不依赖其他 jar
(2)针对接口编程,解耦合
Spring 提供了 IOC 控制反转,由容器管理对象,对象的依赖关系,原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。
(3)AOP 编程的支持
通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付
在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
(4)方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架(如 Struts ,、Hibernate、MyBatis )等的直接支持,简化框架的使用。 Spring 像插线板一样,其他框架是插头,可以容易的组合到一起,需要使用哪个框架,就把这个插头放入插线板,不需要可以轻易的移除
二、IOC控制反转
1、概念
IOC ( Inversion of Control ) :控制反转,是一个理论,一个指导思想,指导开发人员如何使用对象和管理对象的,把对象的创建、属性赋值、把对象的声明周期都交给代码之外的容器管理。
(1)IOC分为控制和反转:
控制:把对象的创建、属性赋值、对象的声明周期管理
反转:把开发人员管理对象的权限转移给了代码之外的容器实现,由容器完成对象的管理
正转:开发人员在代码中,使用 new 构造方法创建对象,开发人员掌握了对象的创建、属性赋值、对象从开始到销毁的全部过程,开发人员有对对象的全部控制
通过容器,可以使用容器中的对象(容器已经创建了对象,对象属性赋值了,对象也已经组装好了)Spring就是一个容器,可以管理对象,创建对象,给属性赋值
(2)IOC的技术实现:
DI(依赖注入):Dependency Injection,缩写是DI,是IOC的一种技术实现,程序只需要提供要使用的对象的名称就可以了,对象如何创建,如何从容器中查找、获取都是由容器内部自己实现
依赖名词:比如说 ClassA 类使用了 ClassB 类的属性或者方法,叫做 ClassA 依赖 Classb
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、Spring的配置文件
<?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、Spring容器创建对象的特点
(1)创建对象 ApplicationContext:接口
通过ApplicationContext对象,获取要使用的其他java对象,执行 getBean(“<bean>的id”)
(2)Spring 默认时调用类的无参构造方法,创建对象
(3)Spring读取配置文件,一次创建好所有的javad对象都放到map中
4、DI:给属性赋值
spring 调用类的无参构造方法,创建对象,对象创建后给属性赋值
给属性赋值可以使用:
(1)xml配置文件中的标签和属性
(2)使用注解
DI分类:
(1)set注入,也叫做设值注入
(2)构造注入
5、基于xml的DI(适用于小项目)
在xml配置文件中使用标签和属性,完成对象创建,属性赋值
(1)set注入,也叫做设值注入(推荐使用)
概念:spring 调用类中的set方法,在set方法中可以完成属性赋值
1.简单类型设值注入
<bean id="mySchool" class="com.tjise.ba02.School"><property name="name" value="北大"/><property name="address" value="北京海淀区"/>
</bean>2.引用类型设值注入<bean id="myStudent" class="com.tjise.ba02.Student"><property name="name" value="李四"/><property name="age" value="21"/><!--引用类型的赋值--><property name="school" ref="mySchool"/><!--setSchool(mySchool)--></bean>
(2)构造注入
构造注入:spring 调用类中的有参构造方法,在创建对象的同时,给属性赋值
<!--构造注入,使用name属性-->
<bean id="myStudent" class="com.tjise.ba03.Student"><constructor-arg name="myname" value="王五"/><constructor-arg name="myage" value="22"/><constructor-arg name="mySchool" ref="mySchool"/></bean><!--构造注入,使用index属性,参数位置,构造方法参数从左往右位置依次是:0,1,2...此处的index可以省略,但是对应的参数顺序不能改变-->
<bean id="myStudent2" class="com.tjise.ba03.Student"><constructor-arg index="0" value="王五"/><constructor-arg index="1" value="22"/><constructor-arg index="2" ref="mySchool"/>
</bean>
(3)引用类型的自动注入
概念:Spring 可以根据某些规则给引用类型完成赋值,只对引用类型有效,两个规则:
(3)–1.byName(按名称注入):java 类中引用类型属性名称和 Spring 容器中 bean 的 id 名称一样,且数据类型也是一样的,这样的bean 能够赋值给引用类型
<!--
byName(按名称注入):java 类中引用类型属性名称和 Spring 容器中 bean 的 id 名称一样,且数据类型也是一样的,这样的bean 能够赋值给引用类型语法:<bean id="xxx" class="yyy" autowire="byName">简单类型属性赋值</bean>
-->
<!--byName自动注入-->
<bean id="myStudent" class="com.tjise.ba04.Student" autowire="byName"><property name="name" value="李四"/><property name="age" value="21"/>
</bean>
<!--声明School-->
<bean id="school" class="com.tjise.ba04.School"><property name="name" value="清华"/><property name="address" value="北京海淀区"/>
</bean>
(3)–2.byType(按类型注入):java 类中引用类型的数据类型和 Spring 容器中 bean 的 class 值是同源关系,这样的 bean 能够赋值给引用类型
<!--
byType(按类型注入):java 类中引用类型的数据类型和 Spring 容器中 bean 的 class 值是同源关系,这样的 bean 能够赋值给引用类型
同源关系:1.java中引用类型的数据类型和bean的class值是一样的2.java中引用类型的数据类型和bean的class值是父子类关系3.java中引用类型的数据类型和bean的class值是接口和实现类的关系语法:<bean id="xxx" class="yyy" autowire="byType">简单类型属性赋值</bean>⚠️注意:在xml配置文件中,符合条件的对象只能有一个,多余一个会报错
--><!--byType自动注入-->
<bean id="myStudent" class="com.tjise.ba05.Student" autowire="byType"><property name="name" value="李四"/><property name="age" value="23"/>
</bean>
<!--声明School,第一种同源关系-->
<bean id="myschool" class="com.tjise.ba05.School"><property name="name" value="北航"/><property name="address" value="北京海淀区"/>
</bean><!--声明School的子类PrimarySchool,第二种同源关系-->
<bean id="primarySchool" class="com.tjise.ba05.PrimarySchool"><property name="name" value="大兴机场"/><property name="address" value="北京大兴区"/>
</bean>
(4)项目中使用多个配置文件
分多个配置文件的方式:
(1)按功能模块分,一个模块一个配置文件
(2)按类的功能分,数据库操作相关的类在一个文件,service 类在一个配置文件,例如配置 redis、事务等等的一个配置文件
Spring 管理多个配置文件:常用的是包含关系的配置文件。项目中有一个总的文件,里面是有import标签包含其他的多个配置文件
总的文件(xml)
<import resource="其他的文件的路径1"/>
<import resource="其他的文件的路径2"/>关键字"classpath":表示类路径,也就是类文件(class)所在的目录,Spring到类路径中加载文件
什么时候使用classpath:在一个文件中要使用其他的文件,需要使用classpath
6、基于注解的DI(适用于大项目)
基于注解的DI:使用 Spring 提供的注解,完成 java 对象创建、属性赋值
注解使用的核心步骤:
1.在源代码加入注解,例如 @Component
2.在 Spring 的配置文件,加入组建扫描器的标签
<context:component-scan base-package="注解所在的包名"/>
(1)创建对象的注解
@Component:适用于对象不是持久层、不是业务层、更不是控制器最合适用@Component@Repository:放在dao接口的实现类上面,表示创建dao对象,持久层对象,能访问数据库@Service:放在业务层的实现类上面,表示创建业务层对象,业务层对象有事务的功能@Controller:放在控制器类的上面,表示创建控制器对象,属于表示层对象控制器对象能接受请求,把请求的处理结果显示给用户,视图层
(2)简单类型属性赋值
@Value
(3)引用类型属性赋值
@Autowried:Spring提供的注解,支持 byName、byType@Autowried:默认就是byType@Autowried、@Qualifiler:使用byName@Resource:来自JDK-1.8及以下版本中的注解,给引用类型赋值的,默认byName@Resource:先使用byName,在使用byType@Resource(name="bean的id"):只使用byName
7、IOC总结
IOC:管理对象的,把对象放在容器中、创建、赋值、管理依赖关系
IOC:通过管理对象,实现解耦合,IOC 解决处理业务逻辑对象之间的耦合关系,也就是 service 和 dao 之间的解藕合
Spring作为容器适合管理对象:
(1)service对象,dao对象
(2)工具类对象
Spring不适合管理的对象:
(1)实体类
(2)servlet、listener、filter等 web 中的对象,他们 tomcat 创建和管理的
三、AOP 面向切面的编程
增加功能导致的问题:
在源代码中,业务方法中增加的功能
1、源代码可能改动较多
2、重复代码较多
3、代码难以维护
1、什么是AOP
AOP ( Aspect Orient Programming ) :面向切面编程
Aspect:表示切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能可以复用。例如:日志功能、事务功能、权限检查、参数检查、统计信息等等
Orient:表示面向、对着
Programming:编程
怎么理解面向切面编程:以切面为核心设计开发你的应用
(1)设计项目时,找出切面的功能
(2)安排切面的执行时间、执行位置
2、AOP作用
(1)让切面功能复用
(2)让开发人员专注业务逻辑,提高开发效率
(3)实现业务功能和其他非业务功能的解耦合
(4)给存在的业务方法,增加功能,不用修改原来的代码
3、AOP中的术语
(1)Aspect:切面,给业务方法增加的功能
(2)JoinPoint:连接点,连接切面的业务方法,在这个业务方法执行时会同时执行切面的功能(一个)
(3)Pointcut:切入点,是一个或多个连接点的集合,表示这些方法执行时,都能增加切面的功能,表示切面执行的位置(一个或多个)
(4)target:目标对象,给哪个对象增加切面功能,这个目标就是目标对象
(5)Advice:通知(增强),表示切面的执行时间,在目前方法执行切面,还是目标方法之后执行切面
AOP中重要的三个要素:Aspect、Pointcut、Advice,这个概念的理解是:在 Advice 的时间,在 Pointcut 的位置执行Aspect
AOP是一个动态的思想,是在程序运行期间,创建代理(ServiceProxy),使用代理执行方法是,增加切面的功能,这个代理对象存在内存中
4、什么时候该用AOP
要给某些方法增加相同的一些功能,源代码不能改或者不方便改时,给业务方法增加非业务功能也可使用AOP
5、AOP技术思想的实现
使用框架实现AOP,实现AOP的框架有很多,有名的两个:
(1)Spring:Spring框架实现AOP思想中的部分功能,Spring框架实现AOP的操作比较繁琐
(2)Aspectj:独立框架,专门做AOP,属于Eclipse
6、使用Aspectj
Aspectj框架可以使用注解和xml配置文件两种方式实现AOP
7、通知
Aspectj 表示切面执行的时间,用的是通知(Advice),可以使用注解来表示
5个注解,表示切面锝个执行时间,这些注解叫做通知注解
@Before:前置通知@AfterRetunring:后置通知@Around:环绕通知@AfterThrowing:异常通知@After:最终通知
8、Pointcut位置
Pointcut 用来表示切面执行的位置,使用 Aspectj 中切入点表达式
切入点表达式语法:execution(访问权限 方法返回值 方法声明(参数) 异常类型)
9、@Before:前置通知
@Before:表示在目标方法之前执行的
/*** @Aspect:切面类的注解* 位置:放在某个类的上面* 作用:表示当前类是一个切面类* 切面类:表示切面功能的类*/
@Aspect
public class MyAspect {//定义方法来表示切面的具体功能/*** 前置通知方法的定义* 1.方法是public* 2.方法是void* 3.方法名称自定义* 4.方法可以有参数,如果有是JoinPoint,也可以没有*//*** @Befor:前置通知* 属性:value 切入点表达式,表示切面的执行位置,在这个方法执行时,同时执行切面的功能* 位置:方法的上面* 特点:1.执行时间是在目标方法之前先执行的* 2.不会影响目标方法的执行* 3.不会修改目标方法的执行结果*//*** 切面类中的通知方法,可以有参数* JoinPoint必须是他* JoinPoint:表示正在执行的业务方法,相当于反射中的 Method* 使用要求:必须是参数列表的第一个* 作用:获取方法执行时的信息,例如方法的名称、方法的参数集合*/@Before(value = "execution(void *(..))")//@Before(value = "execution(void doSome(..))")public void myBefore(JoinPoint joinPoint){//获取方法的定义System.out.println("前置通知中获取目标方法的定义=="+joinPoint.getSignature());System.out.println("前置通知中获取目标方法的名称=="+joinPoint.getSignature().getName());//获取方法执行时参数Object args[] = joinPoint.getArgs();//数组中存放的是 方法的所有参数for (Object object:args){System.out.println("前置通知中获取目标方法的所有参数=="+object);}String methodName=joinPoint.getSignature().getName();if ("doSome".equals(methodName)){//切面的代码System.out.println("doSome()切面前置通知,切面功能在目标方法之前执行 "+new Date());}else if ("doOther".equals(methodName)){System.out.println("doOther()前置通知,方法名称、参数的记录");}}
}
10、@AfterReturning:后置通知
@AfterReturning:表示在目标方法之后执行的
@Aspect
public class MyAspect {//定义方法来表示切面的具体功能/*** 后置通知方法的定义* 1.方法是public* 2.方法是void* 3.方法名称自定义* 4.方法可以有参数,推荐使用Object类型*//*** @AfterReturning:后置通知* 属性:value 切入点表达式* returning 自定义变量,表示目标方法返回值的,自定义变量名称和通知方法的形参名一样* 位置:方法的上面* 特点:1.在目标方法之后执行* 2.能够获得目标方法的执行结果* 3.不会影响目标方法的执行** 方法的参数:Object res :表示目标方法的返回值,使用 res 接收 doOther() 的调用结果,Object res = doOther()** 后置通知执行的顺序:1.Object res = SomeServiceImpl.doOther(..);* 2.myAfterReturning(res);** 思考:1.doOther 方法返回是String Integer Long等基本类型,在后置通知中修改返回值不会影响目标方法的最后调用结果* 2.doOther 返回结果是对象类型,例如Student,在后置通知中修改Student对象的属性值,会不会影响最后调用结果?*/@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")public void myAfterReturning(JoinPoint joinPoint, 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做了添加数据库,我做了备份数据");
// }}}
11、@Around ( 重点 ):环绕通知
@Around(value=“切入点表达式”)
使用环绕通知时,就是调用切面类中的通知方法
@Aspect
public class MyAspect {//定义方法来表示切面的具体功能/*** 环绕通知方法的定义* 1.方法是public* 2.方法是必须有返回值的,推荐使用Object* 3.方法名称自定义* 4.方法必须有ProceedingJoinPoint参数*//*** @Aroun:环绕通知* 属性:value 切入点表达式* 位置:方法定义的上面* 返回值:Object,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)* 参数:ProceedingJoinPoint,相当于反射中的 Method,* 作用:执行目标方法的,等于 Method.invoke()** public interface ProceedingJoinPoint extends JoinPoint {}** 特点:1.再目标方法的前和后都能增加功能* 2.控制目标方法是否执行* 3.修改目标方法的执行结果*/@Around("execution(* *..SomeServiceImpl.doFirst(..))")public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{//获取方法执行时的参数值String name = "";Object args[] = proceedingJoinPoint.getArgs();if (args != null && args.length>0){Object arg = args[0];if (arg != null){name = (String) arg;}}//System.out.println("执行了环绕通知的myAround方法");Object methodReturn = null;System.out.println("执行了环绕通知,在目标方法之前,输出日志时间=="+new Date());if ("ls".equals(name)){//执行目标方法 ProceedingJoinPoint表示doFirstmethodReturn = proceedingJoinPoint.proceed();//Method.invoke(),表示执行doFirst()方法本身}if (methodReturn != null){methodReturn = "环绕通知中修改目标方法原来的执行结果";}System.out.println("执行了环绕通知,在目标方法之后,增加了事务的提交功能");//return "Hello Around,不是目标方法的执行结果";//返回没有修改的目标方法的执行结果return methodReturn;}
}
12、@AfterThrowing
@AfterThrowing(value=“切入点表达式”,throwing=“自定义变量”)
@Aspect
public class MyAspect {//定义方法来表示切面的具体功能/*** 异常通知方法的定义* 1.方法是public* 2.方法是没有返回值 void* 3.方法名称自定义* 4.方法有Exception参数*//*** @AfterThrowing:异常通知* 属性:value 切入点表达式* throwing 自定义变量,表示目标方法抛出的异常,变量名必须和通知方法的形参名一样* 位置:在方法的上面* 特点:1.在目标方法抛出异常后执行的,没有异常不执行* 2.能够获取到目标方法的异常信息* 3.不是异常处理程序,可以得到发生异常的通知,可以发送邮件、短信通知开发人员,看作是目标方法的监控程序*** 异常通知的执行:* try{* SomeServiceImpl.doSecond* }catch(Execution e){* myAfterThrowing(e)* }*/@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "exception")public void myAfterThrowing(Exception exception){System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:"+exception.getMessage());/*** 异常发生可以做:* 1.记录异常出现的时间和位置等信息* 2.发送邮件、短信等通知开发人员*/}
}
13、@After:最终通知
@After(value=“切入点表达式”)
@Aspect
public class MyAspect {//定义方法来表示切面的具体功能/*** 最终通知方法的定义* 1.方法是public* 2.方法是没有返回值 void* 3.方法名称自定义* 4.方法没有参数*//*** @Afte:最终通知* 属性:value 切入点表达式* 位置:在方法的上面* 特点:* 1.在目标方法之后执行的* 2.总是会被执行* 3.可以用来做程序的收尾工作 例如:清除临时数据、变量、清理内存等** 最终通知* try{* void doThird(..)* }finally{* myAfter()* }*/@After(value = "execution(void doThird(..))")public void myAfter(){System.out.println("最终通知,总是会被执行的");}
}
14、@Poincut:定义和管理切入点的注解
@pointcut(value=“切入点表达式”)
@Aspect
public class MyAspect {@Before("myPoincut()")public void myBefore(){System.out.println("前置通知,在你的目标方法之前执行的");}@After(value = "myPoincut()")public void myAfter(){System.out.println("最终通知,总是会被执行的");}/*** @Pointcu:定义和管理切入点的,并不是通知注解* 属性:value 切入点表达式* 位置:在一个自定义方法的上面,这个方法可以看作是切入点表达式的别名,在其他的通知注解中,可以使用方法名称,就表示使用z这个切入点表达式了*/@Pointcut("execution(void doThird(..))")private void myPoincut(){//无需代码,但是自定义的这个方法一般是私有的,不建议public}
15、AOP 总结
AOP是一种动态的技术思想,目的是实现业务功能和非业务功能的解耦合,业务功能是独立的模块,其他功能也是独立模块。例如:事务功能、日志功能等等,让这些功能可以被复用
当目标方法需要一些功能时,可以在不修改或者不能修改源代码的情况下,使用 AOP 技术在程序执行期间,生成代理对象,通过代理执行业务方法,同时增加了功能
四、Spring事务
1、事务的概念
事务:一些 Sql 语句序列的集合,是多条 Sql 作为一个整体执行
事务使用条件:一个操作需要多条 Sql 语句一起完成操作才成功
MySql执行事务
begainTransaction 开启事务
insert into students() value...
select * from students where id=1
update students set name=xxx where id=1
endTransaction 事务结束
2、程序中如何说明事务
事务:加在业务类的方法上面(public方法上面),表示业务方法执行时,需要事务的支持
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();dao2.deleteB();}
}public class AccountDao{public void updateA(){}public void updateB(){}
}public class MoneyDao{public void insertA(){}public void deleteB(){}
}
3、事务管理器
不同数据库的访问技术,处理事务是不同的
(1)使用 jabc 访问数据库,事务的处理
public void updateAccount(){Connection conn=...conn.AutoCommit(false);stat.insert();stat.update();conn.commit();conn.setAutoCommit(true)
}
(2)MyBatis 执行数据库,事务的处理
public void updateAccount(){SqlSession session = SqlSession.openSession(false);try{session.insert("insert into students...")session.update("update students...")session.commit();}catch(Exception e){session.rollback();}
}
4、Spring 统一管理事务
使用 Spring 的事务管理,管理不同数据库访问技术的事务管理,开发人员只需要掌握 Spring 的事务处理一个方案就可以实现实现使用不同数据库访问技术的事务管理
管理事务面向的是 Spring,由 Spring 管理事务的提交、回滚、获取事务状态信息
5、Spring 事务管理器
Spring 框架使用事务管理器对象,来管理所有的事务
事务管理器接口:PlatformTransactionManager
作用:定义了事务的操作,主要是:commit()、rollback()
事务管理器实现类:一种数据库的访问技术有一个实现类,由实现类具体完成事务的提交和回滚,意味着 jdbc、mybatis 访问数据库有自己的事务管理器实现类:DataSourceTransactionManager。例如Hibernate 框架,它的事务管理器实现类:HibernateTransactionManager
6、事务的提交和回滚的时机
什么时候提交事务和回滚事务:当你的业务方法正常执行时没有异常,事务是提交的,如果你的业务方法抛出了运行时异常,事务是回滚的。
异常的分类:
Error:严重错误,回滚事务
Exception:异常类,可以处理的异常情况
(1)运行时异常:RuntimeException和它的子类都是运行时异常,在程序执行过程中抛出异常。常见的运行时异常:NullPointException、NumberFormatException、IndexOutOfBoundException、ArithmeticException
(2)受查异常:编写 java 代码时必须处理的异常,例如:IOException,SqlException,FileFoundException
怎么记忆:方法中抛出了运行时异常,事务回滚,其他情况(正常执行方法,受查异常)依旧提交事务
7、事务使用AOP的环绕通知
环绕通知:可以在目标方法的前后都能增加功能,不需要修改代码
//Spring给业务方法在执行时,增加上事务的切面功能
@Around("execution(所有的业务类中的方法)")
public Object myAround(ProceedingJionPoint pjp){try{PlatformTransactionManager.beginTransaction();//使用Spring事务管理开启事务pjp.proceed();//执行目标方法...PlatformTransactionManager.commit();//业务方法正常执行,提交事务}catch(Exception e){PlatformTransactionManager.rollback();//业务方法正常执行,回滚事务}
8、事务定义接口 TransactionDefinition
TransactionDefinition 接口,定义了三类常量,定义了有关事务控制的属性。给业务方法说明事务属性,和 ACID 不一样
事务的属性:
(1)隔离级别
(2)传播行为
(3)事务的超时
9、隔离级别
隔离级别:控制事务直接影响的程度
5个值只有4个隔离机制
(1)DEFAULT:采用 DB 默认的事务隔离级别,MySql 的默认为 REPEATABLE _ READ;Oracle 默认为 READ _ COMMITTED
(2)READ_UNCOMMITTED:读未提交,未解决任何并发问题。
(3)READ _ COMMITTED:读已提交,解决脏读,存在不可重复读与幻读
(4)REPEATABLE _ READ:可重复读,解决脏读、不可重复读,存在幻读
(5)SERIALIZABLE :串行化,不存在并发问题
10、超时时间
超时时间,以秒为单位,整数值,默认 -1 ,
超时时间:表示一个业务方法最长的执行时间,如果到达时间没有执行完毕,spring 回滚事务
11、传播行为
传播行为有7个值
传播行为:业务方法在调用时,事务在方法之间的传递和使用
使用传播行为,标识方法有无事务
PROPAGATION_REQUIRED
PROPAGATION_SUPPORTS
PROPAGATION_REQUIRES_NEW
以上三个需要掌握的
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOТ_SUPPORTED
(1)REQUIRED:Spring默认传播行为,方法在调用的时候,如果存在事务就使用当前的事务,如果没有事务,则新建事务,方法在新建事务执行
(2)SUPPORTS:支持,方法有事务可以正常执行,没有事务也可以正常执行
(3)REQUIRES_NEW:方法需要一个新事务,如果在调用方法时,存在一个事务,则原来的事务暂停,直到新事物执行完毕,如果方法调用时没有事务,则新建一个事务,在新事物执行代码
12、控制事务@Transactional
@Transactional注解:该注解是 Spring 自带的,使用该注解的属性控制事务(隔离级别,传播行为,超时)
属性:
(1)propagation:事务的传播行为,他使用的 Propagation 枚举值,例如:Propagation . REQUIRED
(2)isolation:表示隔离级别,他使用的是 Isolation枚举值,默认值为 Isolation . DEFAULT
(3)readOnly:boolean类型的值,表示对数据库的操作是否是只读的,默认值为 false
(4)timeout:事务的超时时间,单位为秒,类型为 int ,默认值为—1,即没有时限
(5)rollbackFor:表示回滚的异常类列表,他的值是一个数组,每个值是异常类的class
(6)rollbackForCalssName;表示回滚的异常类列表,他的值是异常类的名称,String类型的值
(7)noRollbackFor:不需要回滚的异常类列表,是class类型的
(8)noRollbackForClassName:不需要回滚的异常类列表,是String类型的值
位置:
(1)在类的上面在
(2)业务方法的上面,在public方法的上面
使用步骤:
(1)在 Spring 的配置文件,声明事务的内容
声明事务管理器,说明使用哪个事务管理器对象
声明使用注解管理事务,开启是注解驱动
(2)在类的源代码中,加入@Transactional
事务的控制模式:
(1)编程式,在代码中编程控制事务
(2)声明式事务,不用编码
例子:
Spring配置文件
<!--声明事务的控制-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--指定数据源DataSource--><property name="dataSource" ref="dataSource"/>
</bean><!--开启事务注解驱动,告诉框架使用注解管理事务-->
<!---transaction-manager:指定事务管理器的id-->
<tx:annotation-driven transaction-manager="transactionManager" />
Java实现代码
public class BuyGoodsServiceImpl implements BuyGoodsService {private SaleDao saleDao;private GoodsDao goodsDao;public void setSaleDao(SaleDao saleDao) {this.saleDao = saleDao;}public void setGoodsDao(GoodsDao goodsDao) {this.goodsDao = goodsDao;}/*** 方式一:* @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, timeout = 20,* rollbackFor ={NullPointerException.class, NotEnougthExcetion.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}** 方式三:使用默认值(多数情况下使用方式三就行了)* @Transactional()**///@Transactional 放在 public 方法的上面,表示方法有事务功能@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, timeout = 20,rollbackFor ={NullPointerException.class, NotEnougthExcetion.class})@Overridepublic void buy(Integer goodsId, Integer nums) {System.out.println("===buy方法的开始===");//生成销售记录Sale sale = new Sale();sale.setGid(goodsId);sale.setNum(nums);saleDao.insertSale(sale);//查询商品Goods goods = goodsDao.selectById(goodsId);if (goods == null){throw new NullPointerException(goodsId+"商品不存在");}else if (goods.getAmount() < nums){throw new NullPointerException(goodsId+"商品库存不足");}//更新库存Goods buyGoods = new Goods();buyGoods.setId(goodsId);buyGoods.setAmount(nums);goodsDao.updateGoods(buyGoods);System.out.println("===buy方法的结束===");}
}
@Transactional使用的特点:
(1)Spring自己提供的事务控制
(2)适合中小型项目
(3)使用方便,效率高
13、使用 Aspect 框架在 Spring 配置文件中,声明事务控制
使用 Aspect 的 AOP,声明事务控制叫做声明式任务
使用步骤:
(1)pom.xml 加入 spring-aspect 依赖
(2)在 spring 配置文件声明事务的内容
1.声明事务管理器
2.声明业务方法需要的事务属性
3.声明切入点表达式
声明式事务:
<?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:tx="http://www.springframework.org/schema/tx"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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><!--加载外部的属性配置文件--><context:property-placeholder location="classpath:jdbc.properties"/><!--声明数据源Datasource--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}" /><property name="password" value="${jdbc.password}" /></bean><!--声明SqlSessionFactoryBean,在这个类的内部去创建SqlSessionFactory--><bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--指定数据源 ref="数据源的id"--><property name="dataSource" ref="dataSource"/><!--指定MyBatis主配置文件,Resource可以直接使用value属性赋值--><property name="configLocation" value="classpath:mybatis.xml"/></bean><!--声明MapperScannerConfigurer SqlSession.getMapper(StudentDao.class)--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!--指定SqlSessionFactory对象的名称--><property name="sqlSessionFactoryBeanName" value="factory"/><!--指定基本包,dao接口所在的包名--><property name="basePackage" value="com.tjise.dao"/></bean><!--声明service--><bean id="buyGoodsService" class="com.tjise.service.impl.BuyGoodsServiceImpl"><property name="saleDao" ref="saleDao"/><property name="goodsDao" ref="goodsDao"/></bean><!--声明式事务:不用写代码--><!--1.声明事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--指定数据源DataSource--><property name="dataSource" ref="dataSource"/></bean><!--2.声明业务方法的事务属性(隔离级别,传播行为,超时)id:给业务方法配置事务段代码起个名称,唯一值transaction-manager:事务管理器的id--><tx:advice id="serviceAdvice" transaction-manager="transactionManager"><!--给具体的业务方法增加事务的说明--><tx:attributes><!--给具体的业务方法,说明他需要的事务属性name:业务方法的名称。配置name的值:1.业务方法的名称 2.带有部分通配符的方法名称 3.使用*propagation:指定传播行为的值isolation:隔离级别read-only:只读,默认falsetimeout:超时时间rollback-for:指定回滚的异常类列表,使用的异常的全限定名称--><tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"read-only="false" timeout="20" rollback-for="java.lang.NullPointerException,com.tjise.exception.NotEnoughException"/><!--在命名业务方法有一定命名规则后,可以对一些方法使用事务--><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"/><!--以上业务方法以外的命名规则--><tx:method name="*" propagation="SUPPORTS" read-only="true"/></tx:attributes></tx:advice><!--声明切入点表达式:表示哪些包中的类,类中的哪些方法参与事务--><aop:config><!--声明切入点表达式id:切入点表达式的名称,唯一值expression:切入点表达式,表示哪些类,类中的哪些方法参与事务--><aop:pointcut id="servicePoint" expression="execution(* *..service..*.*(..))"/><!--关联切入点表达式和事务的通知--><aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePoint"/></aop:config>
</beans>
声明式事务优缺点:
(1)缺点:理解难,配置复杂
(2)优点:代码和事务配置是分开的,控制事务源代码不用修改
能快速了解和掌控项目的全部事务,适合大型项目
五、Spring-Web
1、使用容器对象的问题
(1)创建容器对象的次数多
(2)需要在多个servlet中分别创建容器对象
2、需要什么样的容器对象
(1)容器对象只有一个,创建一次就可以了
(2)容器对象应该在整个项目在共享使用,多个servlet使用同一个容器对象
3、使用监听器解决 ServletContextListener
两个方法:(1)初始时执行的 (2)销毁时执行的
在监听器中,创建好的容器对象,应该放在 web 应用中的 ServletContext 作用域中
4、ContextLoaderListener
ContextLoaderListener 是一个监听器对象,是 Spring 框架提供的,作用:
(1)创建容器对象,一次
(2)把容器对象放入到 ServletContext 作用域
使用步骤:
(1)pom.xml加入依赖 spring-web
(2)web.xml声明监听器
<!--监听器依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.8.RELEASE</version>
</dependency><!--声明监听器默认监听器:创建容器对象时,读取的配置文件:/WEB-INF/applicationContext.xml--><!--自定义容器使用的配置文件的路径context-param:叫做上下午参数,给监听器提供参数的contextConfigLocation:固定名称,表示自定义 Spring 配置文件的路径param-value:自定义配置文件路径可以指定多个配置文件-->
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>