第一章 Spring 概述
1.1 spring特点
spring就是一个java框架,使用java语言开发的, 轻量级的, 开源的框架。 可以在j2se、j2ee项目中都可以使用。
spring又叫做:容器, spring容器装的是java对象。 可以让spring创建java对象, 给属性赋值。管理对象的生命周期。
spring核心技术: ioc, aop
spring作用: 实现解耦合, 解决java对象之间的耦合, 解决模块之间的耦合。
tomcat也是容器:管理的是servlet, filter、 listener 等对象。
1.2 spring的地址
https://spring.io
1.3 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: 控制反转, 是一个指导思想。 指导开发人员如何使用、管理对象的。 把 对象的创建,属性赋值,生命周期都交给容器管理。
1) IoC分为 控制和反转
控制: 对象创建,属性赋值, 声明周期管理
反转:把对象的权限转移给了容器。 由容器完成对象的管理。
正转:开发人员在代码中, 使用 new 构造方法创建对象。 开发人员有对 对象 全部控制权。
2) IoC的技术实现
DI ( 依赖注入) :Dependency Injection。 DI 是IoC的一种技术实现。 程序只需要提供要使用的对象的名称就可以了, 对象如何创建由容器内部自己实现。
3) Spring框架使用的DI实现IoC.
通过spring框架, 只需要提供要使用的对象名词就可以了。 从容器中获取名称对应的对象。
spring底层使用的 反射机制, 通过反射创建对象,赋予属性。
2.2 Spring的配置文件
spring的xml配置文件可以直接在resources上右击新建spring xml配置文件,这样我们就不用自己写文件头这些了,
一般起名为 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
定义beans...
</beans>
spring标准的配置文件:
1)根标签是 beans
2) beans 后面的是约束文件说明
3)beans里面是bean声明。
4)什么是bean: bean就是java对象
xmlns=“http://www.springframework.org/schema/beans” 指定beans命名空间值
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” 指定xsi的命名空间
beans标签和它的子标签都是在 http://www.springframework.org/schema/beans/spring-beans.xsd"定义和说明的
2.3 spring容器创建对象的特点
-
容器对象ApplicationContext:接口 ,一般用其实现类ClassPathXmlApplicationContext,我们在resources目录下的配置文件applicationContext.xml在编译后会在类路径 target/classes 下面,而上面那个类会直接在类路径下找同名称配置文件,并返回容器对象ApplicationContext。
通过ApplicationContext对象,获取要使用的 其他java对象, 执行getBean(“的id”) -
spring默认是调用类的无参数构造方法创建对象,对应于属性的set注入。
-
spring读取配置文件, 创建容器对象的时候一次创建好所有的java对象, 都放到map中,spring容器对象是通过map来管理java对象的。
2.4 DI:给属性赋值。
spring可以通过 xml配置文件 和 注解@ 两种方式注入对象。
而对象java对象 属性的注入 可以采用set注入和构造注入。
2.4.1 基于xml的DI:
在xml配置文件中使用标签和属性,完成对象创建,属性赋值。
1) set注入
set注入: spring调用类中的set方法,在set方法中可以完成属性赋值。
调用的是类的无参构造和对应属性的set方法, 若没有无参构造或set方法会报错。
推荐使用。
<!--简单类型的set注入,使用value-->
<bean id="mySchool" class="com.bjpowernode.ba02.School">
<property name="name" value="北京大学"/>
<property name="address" value="北京的海淀区"/>
</bean>
<!--引用类型set注入,使用ref-->
<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调用类中的有参数构造方法, 在创建对象的同时,给属性赋值
使用构造输入得有有参构造,并且配置文件要和构造方法参数匹配。
<!--构造注入,使用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能够赋值给引用类型。
<!-- 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: 父引用指向子类对象
2: 接口指向实现类对象
3: 同类引用指向其对象
<!-- 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) 项目中使用多个spring配置文件
分多个配置文件的方式: 1)按功能模块分,一个模块一个配置文件。 2) 按类的功能分,数据库操作相关的类在一个文件, service类在一个配置文件, 配置redis,事务等等的一个配置文件。
spring管理多个配置文件: 常用的是包含关系的配置文件。 项目中有一个总的文件, 里面是有impor标签包含其他的多个配置文件。
语法:
总的文件(applicationContext.xml)
<import resource="其他的文件的路径1"/>
<import resource="其他的文件的路径2"/>
关键字“classpath:”:表示类路径, 也就是类文件(target/class文件)所在的目录。 spring到类路径中加载文件
至于什么时候使用classpath: 在一个文件中要使用其他的文件, 需要使用classpath.
当我们有这样一个文件目录的时候,总的配置文件该怎样写:
<?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">
<import resource="classpath:/ba06/spring-school.xml"/>
<import resource="classpath:/ba06/spring-student.xml"/>
<!--包含关系的配置文件亦可以用通配符 *,但总配置文件不能包含在通配符路径中 -->
<import resource="classpath:/ba06/spring-*.xml"/>
<!--意思是当前配置文件包含了另外两个配置文件,配置文件中的bean声明不分顺序-->
</beans>
2.4.2 基于注解的DI
使用注解进行依耐注入时要在配置文件中开启包扫描,使注解生效,该包及其子包都会生效。
<context:component-scan base-package="com.company.beandemo"/>
1.创建对象的注解
@Component 普通java对象,(默认单例模式)
@Respository 持久层对象, 表示对象能访问数据库。
@Service 业务层对象, 处理业务逻辑,具有事务能力
@Controller 控制器对象, 接收请求,显示请求的处理结果。 视图层对象
后三个都是基于 @Component的注解
@Configuration + @Bean通过配置类注入对象
//创建一个class配置文件
@Configuration
public class MyConfiguration{
//将一个Bean交由Spring进行管理
@Bean
public MyBean myBean(){
return new MyBean();
}
}
2.简单类型属性赋值
@Value("${user.name}")美元符号在springboot中可以直接在配置文件中取值
3.引用类型赋值
@Autowired: spring提供的注解 . 支持byName, byType,主使用 @autowired
@Autowired: 默认就是byType
@Autowired + @Qualifier : 使用byName
@Resource : 来自jdk中的注解,给引用类型赋值的。默认是byName
@Resource: 先使用byName,没有符合条件的再byType
@Resource(name="bean的名称") :只使用byName注入
2.5 IoC 总结
IoC:管理对象的声明周期,把对象放在容器中,创建,赋值,管理依赖关系。
IoC:通过管理对象,实现解耦合。
spring作为容器适合管理什么对象 ?
1)service对象 ,dao对象。
2)工具类对象。
不适合交给spring的对象 ?
1)实体类。
2)servlet ,filter, listener 等web中的对象。他们是tomcat创建和管理的。
第三章 AOP 面向切面编程
3.1 增加功能,导致的问题
在源代码中, 业务方法中增加常用的的复用功能,比如打印日志,事务管理, 权限检查,参数检查。
1)源代码可能改动的比较多。
2)重复代码比较多。
3)代码难于维护。
3.1 什么是AOP
Aspect : 给业务方法增加的功能,叫做切面。 切面一般都是非业务功能, 而且切面功能一般都是可以复用的。 例如 日志功能, 事务功能, 权限检查,参数检查, 统计信息等等。
我们面向切面编程时需要关注切面、切入点等信息。
3.2 AOP作用
1)让切面功能复用
2)让开发人员专注业务逻辑。 提高开发效率
3)实现业务功能和其他非业务功能解耦合。
3.3 AOP中术语
1)Aspect:切面, 给业务方法增加的复用功能。
2)JoinPoint:连接点, 连接切面的业务方法。 在这个业务方法执行时,会同时执行切面的功能。
3)Pointcut: 切入点, 是连接点集合。 表示这些方法执行时,都能增加切面的功能。切入点是一个集合,可以通配 表达式表示, 比如“execution=…”。
4)Advice:通知(增强),表示切面的执行时间。 在目标方法之前执行切面,还是目标方法之后执行切面。
AOP中重要的三个要素: Aspect, Pointcut , Advice. 这个概念的理解是: 在Advice的时间,在Pointcut的位置, 执行Aspect
AOP是一个动态的思想。 在程序运行期间,创建代理(ServcieProxy),在代理对象中增加切面的功能。这个代理对象是存在内存中的。我们使用 applicationContext.get(beanname) 获取对象发现对象类型变成了代理类。
3.4 什么时候你想用AOP
你要给某些方法 增加相同的一些功能。 源代码不能改。 给业务方法增加常用的非业务功能,也可以使用AOP
3.5 AOP技术思想的实现
使用框架实现AOP。 实现AOP的框架有很多。 有名的两个
1) Spring : Spring框架实现AOP的操作比较繁琐,笨重。只有spring自己用。
2) Aspectj : 独立的框架,专门是AOP。 属于Eclipse ,简单易用。
3.6 使用AspectJ框架实现AOP
AspectJ框架可以使用 注解(推荐)和 xml配置文件 两种方式 实现 AOP
3.6.1 通知
Aspectj中表示切面执行时间,用的通知(Advice)。 这个通知可以使用注解表示。
通知Advice包含5个注解, 表示切面的5个执行时间, 这些注解叫做通知注解。
@Before : 前置通知
@AfterRetunring: 后置通知
@Around: 环绕通知
@AfterThrowing: 异常通知
@After: 最终通知
3.6.2. Pointcut 位置
Pointcut 用来表示切面执行的位置,即增加切面的业务方法。 使用Aspectj中切入点表达式。
切入点表达式语法: execution(访问权限 方法返回值 方法声明(参数) 异常类型)
几个切入点表达式的例子, *表示该项任意, ..表示通配符
execution( public * *(..))
指定切入点:任意公共方法
execution( * set*(..))
指定切入点:任意以set开始的方法
execution( * com.xyz.service.*.*(..))
指定切入点:定义在service包里的任意类的任意方法。
execution( * com.xyz.service..*.*(..))
指定切入点:定义在service包或子包里的任意类的任意方法。
3.6.3 @Before前置通知
前置通知@Before:在目标方法前执行,不影响目标方法的执行,不修改目标方法的执行结果.
/**
* 切面类中的通知方法,可以有参数
* JoinPoint必须是他。
*
* JoinPoint: (连接点)表示正在执行的业务方法。 相当于反射中 Method
* 使用要求:必须是参数列表的第一个
* 作用:获取业务方法执行时的信息,例如方法名称,参数集合等。
*/
@Component //将切面类交于spring
@Aspect //定义aop切面类
public class MyAspect{
@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);
}
//根据methodName 决定做什么
String methodName = jp.getSignature().getName();
if("doSome".equals(methodName)){
//切面的代码。
System.out.println("doSome输出日志=====在目标方法之前先执行==:"+ new Date());
} else if("doOther".equals(methodName)){
System.out.println("doOther前置通知,作为方法名称,参数的记录。");
}
}
}
使用aspectj的aop时需要在spring配置文件中开启aop自动代理
<!-- 开启注解自动扫描注入bean -- >
<context:component-scan base-package="com.company.beandemo"/>
<!-- 开启aspectj aop自动代理生成器 -- >
<aop:aspectj-autoproxy />
<!-- 调用sapectj框架功能,寻找spring容器中所有目标对象(切入点),把每个对象加入切面类中的功能,生成代理。
这个代理是修改内存中的目标对象,通过容器获得的对象是代理对象! (com.sun.proxy.$Proxy8)-- >
3.6.4 @AfterReturning后置通知
@AfterReturning: 在目标方法之后执行的
/* 特点:
* 1.在目标方法之后,执行的。
* 2.能获取到目标方法的执行结果。
* 3.不会影响目标方法的执行
*
* 方法的参数:
* JoinPoint 连接点即目标方法,必须在第一个。
* Object res: 表示目标方法的返回值,使用res接收doOther的调用结果。
*
* 后置通知的执行顺序
* 代理对象Proxy.doOther(){
* Object res = SomeServiceImpl.doOther(..);
* myAfterReturning(res); //后置通知不修改基本类型的res,引用类型的不行。
* return res;
* }
* 思考:
* 1 doOther方法返回是String ,Integer ,Long等基本类型,
* 在后置通知中,修改返回值, 是不会影响目标方法的最后调用结果的。
* 2 doOther返回的结果是对象类型,例如Student。
* 在后置通知方法中,修改这个Student对象的属性值,会不会影响最后调用结果?
*/
@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做了添加数据库, 我做了备份数据");
}
}
public static void main(String[] args){
ApplicationContext ioc=new ClassPathXmlApplicationContext(applicationContext.xml);
Service service = ioc.getBean(someServiceImpl);
String res=service.doOther();
Sytem.out.println(res);
--结果为"abcd"而不是"HELLO Aspectj"--
//即在代理的后置通知里修改目标方法的res,对于基本类型的res是不会影响最终获得的res的。
}
3.6.5 @Around 环绕通知
@Around(value=“切入点表达式”)
使用环绕通知: 就是调用 切面类中的通知方法,在切面类中决定是否调用目标方法和修改返回结果。
特点:
- 1.在目标方法的前和后都能增强功能
- 2.控制目标方法是否执行
- 3.修改目标方法的执行结果。
/**
* @Around:环绕通知
* 属性:value 切入点表达式
*
* 返回值:Object ,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
* 参数: ProceedingJoinPoint, 相当于反射中 Method。
* 作用:执行目标方法的,等于Method.invoke()
*
* public interface ProceedingJoinPoint extends JoinPoint {}
*/
@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=“自定义变量”)
/**
* @AfterThrowing:异常通知
* 属性: value 切入点表达式
* throwing 自定义变量,表示目标方法抛出的异常。
* 变量名必须和通知方法的形参名一样
* 特点:
* 1. 在目标方法抛出异常后执行的, 没有异常不执行
* 2. 能获取到目标方法的异常信息。
* 3. 不是异常处理程序。可以得到发生异常的通知, 可以发送邮件,短信通知开发人员。
* 看做是目标方法的监控程序。
*
* 代理对象中异常通知的执行
* try{
* SomeServiceImpl.doSecond(..)
* }catch(Exceptoin e){
* myAfterThrowing(e);
* }
*/
@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=“切入点表达式”),在代理对象中相当于是 try-finally 结构。
@After(value = "mypt()")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
/**
* @Pointcut: 定义和管理切入点,不是通知注解。
* 属性: value 切入点表达式
* 位置: 在一个自定义方法的上面, 这个方法看做是切入点表达式的别名。
* 其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式了
*/
@Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
private void mypt(){
//无需代码
}
第四章 Spring集成MyBatis
4.1 集成思路
spring能集成很多的框架,是spring一个优势功能。 通过集成功能,让开发人员使用其他框架更方便。
集成使用的是spring ioc 核心技术。spring集合mybatis主要是将 datasource、sqlSessionFactory、dao接口代理对象这些对象注入到spring容器中,最终进行curd的便是dao接口代理对象(mapper)。由mybatis框架生成。
4.2未集成时
mybatis没有经spring集成之前,也可以生成dao代理对象进行crud,只不过我们得自己创建datasource、sqlSessionFactory、dao接口代理对象。下面是没有spring时的mybatis配置文件,datasource和每一个mapper.xml(或者mapper所在的包)都得在mybatis核心配置文件中注册。
通过SqlSessionFactoryBuilder(mybatis配置文件)便可创建sqlSessionFactory对象,我们可以把sqlSessionFactory看成是数据库连接池,一个项目中应该只有一个,sqlSessionFactory.openSession()便可得到sqlSession,他直接和数据库打交道,相当于连接池的连接器,每个线程都应该有自己的sqlSession实例,sqlSession不是线程安全的,因此不能被共享,因此我们每当要crud时当场打开一个sqlSession,用完后就给关闭,避免占用sqlsessionFactory的资源。
dao接口 proxy对象 = sqlSession.getMapper( dao接口.class )
这样便会根据 (dao接口和对应的mapper.xml) 创建dao接口代理对象,通过dao代理对象直接执行mapper.xml中对象的sql。经检测发现代理的 proxy对象.getClass() 结果是 org.apache.ibatis.binding.MapperProxy.
4.3 spring集成mybatis
使用mybatis,需要创mybatis框架中的某些对象, 使用这些对象,就能使用mybatis提供的功能了。
分析: mybatis执行sql语句,需要使用那些对象
-
需要有Dao接口的代理对象, 例如StudentDao接口, 需要一个它的代理对象
使用 SqlSession.getMapper(StudentDao.class),得到dao代理对象
-
需要有SqlSessionFactory, 创建SqlSessionFactory对象,才能使用openSession()得到SqlSession对象。
-
数据源DataSource对象, 使用一个更强大,功能更多的连接池对象(druid)代替mybatis自己的
PooledDataSource,况且 SqlSessionFactory 需要 dataSource。
集成 maybatis 时 spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
-<beans 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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans">
<!--声明数据源DataSource为druid替换mybatis自带的PolledDataSource-->
<!--druid不需要driver-class-name,它可以根据url自动判断驱动类名称-->
<bean destroy-method="close" init-method="init" class="com.alibaba.druid.pool.DruidDataSource" id="myDataSource">
<property value="jdbc:mysql://localhost:3306/springdb" name="url"/>
<property value="root" name="username"/>
<property value="555888" name="password"/>
</bean>
<!--声明SqlSessionFactoryBean,在这个类的内部,创建SqlSessionFactory-->
-<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean" >
<!--指定数据源-->
<property name="dataSource" ref="myDataSource"/>
<!--指定mybatis核心配置文件 这里可以不要了,dataSource足够创建SqlSessionFactory -->
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
//对应于没集成时的这句话
SqlSessionFactory factory = new SqlSessonFactoryBuider.build(classpath:mybatis.xml)
<!--mybatis的 mapperScanner,使用sqlSessionFactory为dao包下的所有dao接口生成代理并注入容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象的名称-->
<property value="factory" name="sqlSessionFactoryBeanName"/>
<!--指定基本包,dao接口所在的包名-->
<property value="com.bjpowernode.dao" name="basePackage"/>
</bean>
</beans>
MapperScannerConfigurer 作用是:循环basePackage所表示的包,把包中的每个接口都找到,调用SqlSession.getMapper 把每个dao接口都创建出dao代理对象 ,并把代理对象放到spring容器中。
ApplicationContext ctx = .... //获取容器对象
SqlSessionFactory sqlSessionFactory = ctx.getBean("factory"); //获取sqlSessionFactory
SqlSession session = sqlSessionFactory.openSession(); //获取SqlSession
for(接口: com.bjpowernode.dao){
dao接口 proxy对象 = session.getMapper(接口) //执行sqlSession.getMapper便会生成代理对象
springMap.put(dao接口名的首字母小写, 对象) //把代理对象注入容器
}
第五章 Spring 事务
5.1 事务的概念
什么事务? 事务是一些sql序列的集合, 是多条sql, 作为一个整体执行。
5.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();
}
}
public class AccountDao{
public void updateA(){}
public void updateB(){}
}
public class MoneyDao{
public void insertA(){}
public void deleteB(){}
}
5.3 事务管理器
5.3.1 不同的数据库访问技术 jdbc、mybatis、hibrnate等,事务处理方案 是不同的。
1)使用jdbc访问数据库, 事务处理。
public void updateAccount(){
Connection connection = ...
connection .setAutoCommit(false);
statement.insert()
statement.update();
connection .commit();
connection .setAutoCommit(true)
}
- mybatis访问数据库,事务处理。
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管理事务,统一事务管理器接口。
5.3.3 Spring事务管理器
Spring框架使用事务管理器对象管理所有的事务。
事务管理器接口: PlatformTransactionManager 定义了事务的操作, 主要是commit(), rollback()
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
spring事务管理器接口有很多实现类:各数据库的访问技术有一个实现类。 由各实现类完成具体的事务提交,回滚。
jdbc或者 mybatis(算是jdbc的高级封装) 持久层技术的事务管理器实现类 : DataSourceTranactionManager
hibernate框架的事务管理器实现类: HibernateTransactionManager.
spring统一事务管理器接口,各持久层框架实现类完成提交、回滚。
5.3.4 事务的提交和回顾的时机
什么时候提交事务,回滚事务 ?
当你的业务方法正常执行时,没有异常,事务是提交的。 如果你的业务方法抛出了运行时异常, 事务是回滚的。
异常分类:
Error: 严重错误。 回滚事务。
Exception : 异常类,可以出来的异常情况
运行时异常: RuntimeException和他的子类都是运行时异常, 在程序执行过程中抛出的异常。 常见的运行时异常:
NullPoinerException , NumberFormatException , ArithmeticException, IndexOutOfBoundsException.
受查异常 : 编写java代码的时候,必须出来的异常。 例如IOException , SQLException , FileNotFoundException ,须在代码里处理
怎么记忆:
方法中抛出了运行时异常, 事务回滚,其他情况(正常执行方法,受查异常)就是提交事务。
5.3.5 spring事务管理底层使用的AOP的环绕通知
环绕通知:可以在目标方法的前和后都能增强功能,不需要修改代码代码
spring在执行业务方法时,增加上事务切面功能,或者说spring给有 @Transaction注解 的类生成代理,在环绕通知中实现事务功能。
@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接口。定义了三类静态常量, 定义了有关事务控制的属性。(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注解, 使用注解的属性控制事务(隔离级别,传播行为,超时)
属性:
1.propagation : 事务的传播行为, 他使用的 Propagation类的枚举值。例如 Propagation.REQUIRED
2.isolation : 表示隔离级别, 使用Isolation类的枚举值,表示隔离级别。 默认 Isolation.DEFAULT
3.readOnly: boolean类型的值,表示数据库操作是不是只读的。默认是false
4.timeout: 事务超时,默认是-1, 整数值,单位是秒。 例如 timeout=20
5.rollbackFor: 表示回滚的异常类列表, 他的值是一个数组,每个值是异常类型的class。
6.rollbackForClassName: 表示回滚的异常类列表,他的值是异常类名称,是String类型的值
7.noRollbackFor: 不需要回滚的异常类列表。 是class类型的。
8.noRollbackForClassName: 不需要回滚的异常类列表,是String类型的值
注解的使用步骤:
1)在spring的配置文件,声明事务的内容(声明式事务)
声明事务管理器,说明使用哪个事务管理器对象,mybatis 使用 DataSourceTransactionManager
声明使用注解管理事务, 开启是事务注解驱动,即使 @Transactional 生效
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源DataSource-->
<property name="dataSource" ref="myDataSource" />
</bean>
<!--开启事务注解驱动: 告诉框架使用注解管理事务-->
<tx:annotation-driven transaction-manager="transactionManager" />
2)在类的源代码中,在public的业务方法上加入@Transactional。由于开启了事务注解驱动,事务便会生效,而且容器中有事务管理器,这样spring会给目标对象生成动态代理,增加事务切面功能,并且代理中一定有事务管理器引用,来进行提交、回滚。
//@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) { }