目录
概述
什么是spring
侵入式的概念
spring的核心
spring的优势
注意
IOC控制反转
概述
核心
容器
DI,dependency injection依赖注入
概念
注入方式
循环依赖
spring如何解决循环依赖
spring生成Bean的方式
Bean属性注入(Bean属性赋值)
springBean生命周期
实例化Bean
属性赋值(依赖注入)
Aware接口回调
BeanPostProcessor前置处理器
InitializingBean(初始化)
BeanPostProcessor后置处理器
destroy-method销毁
springBean的作用域
springIOC注解
类注解
@Component
@Configuration
@Scope
@Scope具有以下几种作用域
@Scope注意点
属性注解
@Value
@AutoWire
编辑
@Resource
编辑
Resource总结
@Autowire和@Resource区别
方法注解
@Bean
@Scope
@Lazy
@Primary
@Profile
AOP面向切面
底层实现
代理
静态代理
动态代理
JDK 动态代理
Cglib 动态代理
动态代理注意点
AOP名词
Joinpoint(连接点)
Pointcut(切入点)
Advice(通知/增强)
Introduction(引介)
Aspect(切面)
Weaving(织入)
Proxy(代理)
简单的springAOP实现
@Aspect
@Pointcut("xxx")
@Before等
其他通知注解
切点表达式
依赖
实现
spring事务管理
概述
什么是事务
事务特性ACID
不考虑事务隔离引发的安全性问题
脏读
不可重复读
幻读
事务隔离级别
事务回滚
spring事务核心
PlatformTransactionManager平台事务管理器接口
TransactionDefinition事务管理的属性
transactionManager
isolation,隔离级别
propagation,传播行为
readOnly,是否只读事务
timeout,超时设置
rollbaclFor,何种异常执行回滚
怎么使用spring事务管理
依赖
简单demo
spring事务失效
数据库方面
spring方面
配置方面
代码编写方面
异常处理
通过springAOP实现自动事务,默认情况下不再需要对每个方法手动打注解
spring中运用的设计模式
概述
什么是spring
一站式轻量级分层框架,非侵入式
spring是非侵入式的框架
侵入式的概念
侵入式:实现特定接口,继承特定类,改变原有类的结构,即为侵入式,如struts2
非侵入式:对类原本结构没有影响,仍然增强了JavaBean的功能
spring的核心
核心是IOC控制反转和AOP面向切面
spring的优势
IOC,解耦开发
AOP,方便权限拦截,运行监控
声明式事务管理
方便集成其它框架
降低日常开发难度
注意
因为当下项目已经全面普及springboot,前后端分离,因此我们不再关注以前ssm项目中常见的xml配置文件,比如applicationContext.xml,只关注spring本身
IOC控制反转
概述
在没有spring的情况下,如果需要使用一个类的对象,需要手动的new Xxx(),那么当前类就和Xxx类形成了强耦合
于是有人想到用BeanFactory模式,通过BeanFactory获取对象,这样当前类就和Xxx解耦,比如
private XxDao xxDao = DaoFactory.getInstance().createDao("...dao.impl.XxDaoImpl", XxDao.class);
spring提供了更好的解决方案,即IOC控制反转,将对象的管理权反转给了spring,开发者不再需要手动创建Bean,而是直接从spring容器获取
核心
IOC的核心是spring容器,spring创建对象并负责管理它们完整的生命周期
IOC需要依赖注入(DI)的支持:创建A对象,A会用到其他的类,这时就需要依赖注入支持
容器
ApplicationContext
即时加载,加载applicationContext.xml(容器启动)时候就会创建对象
ClassPathXmlApplicationContext:加载类路径下 Spring 的配置文件.
FileSystemXmlApplicationContext:加载本地磁盘下 Spring 的配置文件
DI,dependency injection依赖注入
概念
依赖:指一个类使用到了另一个类,A类依赖B类,即A类使用到了B类
依赖注入:将类和类的依赖关系告诉spring容器
注入方式
构造方法:通过构造方法将另一个类的对象传入当前类
set方法:通过set方法将另一个类的对象传入当前类
注解注入:通过注解获取另一个类的对象
注解方式只能注入spring容器管理的类,因此要配置扫描包,springboot默认会扫描启动类所在包及其子包下的Bean,可以自定义配置
@ComponentScan(value = {"com.xxx.app.service.*", "com.xxx.app.controller.*"})
public class UserServiceImpl implements UserService {
//注解注入
@Autowired
private UserDao userDao;
//使用构造方法实现依赖注入
public UserServiceImpl(UserDao userDao){
this.userDao = userDao;
}
//使用set方法实现依赖注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
循环依赖
定义:A依赖B,B依赖A
spring如何解决循环依赖
构造器的循环依赖:无法处理,直接抛出BeanCurrentlylnCreationException异常
单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖
非单例循环依赖:无法处理
spring生成Bean的方式
通过无参构造创建Bean
使用BeanFactory:静态和非静态工厂
Bean属性注入(Bean属性赋值)
构造方法:通过构造方法给属性赋值
set方法:通过set方法给属性赋值
spel
springBean生命周期
实例化Bean
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean
属性赋值(依赖注入)
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入
Aware接口回调
Spring会检测该对象是否实现了xxxAware接口,如果Bean实现了Aware接口,容器会回调相应的方法,将容器相关的信息注入到Bean中
如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字
如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身
如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文
BeanPostProcessor前置处理器
容器会调用所有实现了BeanPostProcessor接口的类的postProcessBeforeInitialization方法,对Bean进行前置处理
InitializingBean(初始化)
如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法
或在Bean定义中(配置)指定init-method方法
BeanPostProcessor后置处理器
如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法
由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术
在这一步后,Bean就被创建完成,可以开始使用这个Bean了
destroy-method销毁
当容器关闭时,会调用Bean的销毁方法
如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法
或在Bean定义中指定destroy-method方法
springBean的作用域
singleton:默认作用域,单例bean,每个容器中只有一个bean的实例
prototype:为每一个bean请求创建一个实例
request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收
session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例
global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中
springIOC注解
刚刚我们说,IOC是将对象的控制从开发者反转给spring,我们通过spring提供的注解来通知spring管理哪些类,将哪些类以哪种作用域向容器中注入Bean
类注解
@Component
向spring容器注入当前Bean
@Component("user"),相当于xml配置了<bean name="user"...></bean>,现在是否理解xml中的注入?其实就是通过xml的方式向spring容器注入了一个Bean
Spring 中提供@Component 的三个衍生注解,功能和@Component一样
@Controller 控制层
@Service 业务层
加在接口的实现类上,不加在接口上
对于有多个实现类的接口,要指定name,自动装配要使用@Resource(name="xxx")
@Repository 持久层
说明:为了区别于类型,通常配置name属性为开头小写
实现类通常配置name=接口名,存在多个实现类则根据区别配置不同name
@Configuration
标记这是一个配置类,一般会配合@Bean,@Scope,@Lazy使用,等价于原本的xml配置文件注入Bean
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
相当于xml
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@Scope
@Scope作用在类上,表示给定容器中Bean的作用域
@Scope(scopeName="prototype");
@Scope具有以下几种作用域
singleton 单实例的(单例)(默认),全局有且仅有一个实例
prototype 多实例的(多例),每次获取Bean的时候会有一个新的实例
reqeust 同一次请求,每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
session 同一个会话级别,每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
@Scope注意点
1.尽量不要直接使用字符串配置,使用spring提供的参数
ConfigurableBeanFactory.SCOPE_PROTOTYPE
ConfigurableBeanFactory.SCOPE_SINGLETON
WebApplicationContext.SCOPE_REQUEST
WebApplicationContext.SCOPE_SESSION
2.每一个Bean实例,都会有一个initMethod() 和 destroyMethod() 方法,我们可以在Bean 类中自行定义,也可以使用 Spring 默认提供的这两个方法
也可以通过@Bean的属性指定@Bean(initMethod = "initUser", destroyMethod = "destroyUser")
3.思考这么一个场景:当Controller中定义了非静态成员变量,如果不设置成多例,那么多次调用操作同一个变量,会不断的改变变量的值,与我们调用方法的预期不符
属性注解
@Value
用于注入普通类型
@Value(value="zs"),value=可以省略
作用在指定成员的成员变量上或者对应的set()上,区别在于
作用成员变量上,通过反射Field注入
作用在set()上,通过set()注入
可以获取配置文件的数据进行注入,比如.properties
@AutoWire
@Autowire默认按照类型(by-type)装配
默认情况下要求依赖对象必须存在,如果找到多个,再按照名称装配
@Autowire
private StudentService studentService;
如果允许依赖对象为null,需设置required属性为false,即
@Autowire(required=false)
private StudentService studentService;
如果使用按照名称(by-name)装配,需结合@Qualifier注解使用,即
@Autowire
@Qualifier("studentService") //这里的参数是bean的name,studentService
private StudentService studentService;
@Resource
默认按照名称(by-name)装配,名称可以通过name属性指定
当按照名称(by-name)装配未匹配时,按照类型(by-type)装配,此时就是用成员的类型去容器中bean的类型匹配
@Resource(name="studentService") //用这里的参数studentService去容器和bean的name匹配
当显式指定name属性后,只能按照名称(by-name)装配
如果没有指定name
当@Resource注解在成员变量上时,默认取name=成员变量名称装配,这里是字段名称不是字段类型,如下就是用studentService去和容器中bean的name匹配
当@Resource注解在setter方法上时,默认取name=属性名称装配
@Resource
private StudentService studentService;
@Resource
public void setXXX() {...}
Resource总结
如果同时指定name和type属性,则找到唯一匹配的bean装配,未找到则抛异常
如果指定name属性,则按照名称(by-name)装配,未找到则抛异常
如果指定type属性,则按照类型(by-type)装配,未找到或者找到多个则抛异常(一个type可能有多个实现类,所以可能找到多个结果)
既未指定name属性,又未指定type属性,则按照名称(by-name)装配,如果未找到,则按照类型(by-type)装配
@Autowire和@Resource区别
1.来源不同:@Autowire是spring提供的注解,@Resource是jdk提供的注解
2.默认的依赖查找顺序不同:
@Autowire默认根据类型查找,如果找到多个,再根据名称查找
@Resource默认根据名称查找,找不到再根据类型查找
3.支持的参数不同
@Autowire仅支持require参数
@Resource支持多个参数,比如name,type
4.支持的注入方式不同
@Autowire支持属性注入,setter注入,构造注入
@Resource不支持构造注入,即这个标签不能打在构造方法上实现注入
方法注解
@Bean
方法级别注解,通常使用在@Configuration的配置类中使用
向容器注入一个Bean,id为方法名,类型为方法返回值
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
相当于xml
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@Scope
@Scope作用在方法上,表示当前Bean作用域
@Lazy
@Lazy作用在方法上,表示当前Bean在注入容器时是懒加载模式
@Primary
当某个类型的Bean有多个候选者可以注入时,应当优先加载@Primary标记的Bean
@Profile
当spring.profile.active为对应的值时,才向容器注入当前Bean
AOP面向切面
切面编程并不是spring开创的,来源是java拦截器思想,但spring给切面编程提供了更好的解决方式
抽取程序执行过程中多个部分功能一致的代码,比如:权限校验,日志记录,性能监控,事务控制
底层实现
通过代理机制,springAOP会用到两种代理机制,只能选一种不能混用
代理
什么是代理:将原本的调用,比如person.run();封装一层代理,在代理对象中调用person.run();
静态代理
代码运行前,我们就编写好代理类,编译后生成class文件
缺点十分明显:每一个被代理对象都需要建一个代理类去代理,代码冗杂
动态代理
代理类是在运行过程中产生的,不需要给每一个被代理类编写单独的代理类
JDK 动态代理
针对接口的实现类产生代理
生成接口的实现类,在实现类中通过method.invoke(...)调用被代理对象的方法
Cglib 动态代理
针对没有实现接口的类产生代理
对目标进行继承代理,应用的是字节码增强生成当前类的子类对象
Enhancer类的create()生成代理对象
实现MethodInterceptor接口,重写intercept(...)
methodProxy.invoke(target,objects)调用代理对象方法
如果没有Spring,那么需要我们手动的Proxy.newProxyInstance(xx.xx.xx);
简单来说,就是在目标方法执行时,会通过代理的方式生成一个子类,重写目标方法,在方法体前后执行AOP代码
动态代理注意点
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
Spring AOP的原理是 JDK 动态代理和CGLIB字节码增强技术,前者需要被代理类实现相应接口,也只有接口中的方法可以被JDK动态代理技术所处理;后者实际上是生成一个子类,来覆盖被代理类,那么父类的final方法就不能代理,因为父类的final方法不能被子类所覆盖。一般而言Spring默认优先使用JDK动态代理技术,只有在被代理类没有实现接口时,才会选择使用CGLIB技术来实现AOP,这样就可能造成代理混用的问题,spring提供了一个配置强制使用cglib代理来解决混用问题:<aop:config proxy-target-class="true" />
AOP名词
Joinpoint(连接点)
目标对象中所有可以增强的方法都是连接点
所谓连接点是指那些可以被拦截到的点,因为spring只支持方法类型的连接点
Pointcut(切入点)
目标对象中已经被增强的方法
所谓切入点是指我们要对哪些连接点Joinpoint进行拦截的定义
Advice(通知/增强)
负责增强的代码
所谓通知是指拦截到Joinpoint之后所要做的事情
通知分为前置通知,后置返回通知,后置通知(最终通知),环绕通知(切面要完成的功能),异常通知
后置返回通知:目标方法出现异常不会执行
后置通知(最终通知):目标出现异常也会执行
Introduction(引介)
引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或 Field
Aspect(切面)
是切入点和通知(引介)的结合
Target(目标对象)
被代理的目标对象
Weaving(织入)
将通知应用到切点的过程就是织入
是指把增强应用到目标对象来创建新的代理对象的过程,织入完成生成代理对象
Proxy(代理)
通知织入到目标对象以后,形成代理对象
一个类被AOP织入增强后,就产生一个结果代理类
简单的springAOP实现
@Aspect
定义切面,表示该类是切面类
这个注解只是声明这是个切面类,并没有放进Spring容器,通常使用@Component将该类交给spring容器
切面类中可以定义切点,通知
@Pointcut("xxx")
定义切点,两种方式
execution 指定方法,@Pointcut("execution(* com.coolway.*(..))")
@annotation 指定注解,@Pointcut("@annotation(com.coolway.annotation.RequiresLogin)")
@Before等
定义通知,即指定切点处要执行的AOP代码
要给通知注解传入切点
要么使用上面@Pointcut("xxx")定义好的切点
要么重新传入切点,比如@Around("@annotation(com.coolway.annotation.RequiresLogin)")
其他通知注解
@AfterReturning
@Around
@AfterThrowing
@After
切点表达式
完整的表达式:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的可见性修饰符,必须是public,通常省略
void com.coolway.service.impl.StudentServiceImpl.getStudent()
ret-type-pattern:方法的返回值类型,如 int,void 等;通常不对返回值做要求,用*表示任意类型返回值
* com.coolway.service.impl.StudentServiceImpl.getStudent()
declaring-type-pattern:方法所在类的全路径名,类名可以定义为部分匹配,比如以ServiceImpl结尾
* com.coolway.service.impl.*ServiceImpl.*(..)
可以配置成包括子包下符合的,使用..符号,如service下任意子包下的impl包的ServiceImpl类的任意方法(任意参数)
* com.coolway.service..impl.*ServiceImpl.*(..)
name-pattern:方法名,如 getOrderDetail();可以用*表示该类的任意方法
* com.coolway.service.impl.StudentServiceImpl.*()
param-pattern:方法的参数类型,如 java.lang.String;..表示任意参数
* com.coolway.service.impl.StudentServiceImpl.*(..)
throws-pattern:方法抛出的异常类型,如 java.lang.Exception;
示例:execution(public void com.coolway.service.impl.StudentServiceImpl.getStudent())
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
实现
@Aspect
public class AspectDemo {
//定义切点,指定方法
@Pointcut("execution(* com.coolway.*(..))")
public void loginAspectPointcut() {}
//使用定义好的切点
@Before("loginAspectPointcut()")
public void beforeLogin() {
System.out.println("开始访问,查看是否登录");
}
//指定切点为注解
@After("@annotation(com.coolway.annotation.RequiresLogin)")
public void afterLogin() {
System.out.println("结束访问");
}
}
spring事务管理
概述
什么是事务
逻辑上的一组操作,一起成功,一起失败
显然,通常执行一系列的增删改操作,需要开启事务
spring事务管理依托于数据库事务,是通过数据库连接Connection来实现的,并不是spring自己提供的事务,spring提供了更好的方式处理事务
事务特性ACID
原子性:事务不可分割
一致性:事务执行前后数据完整性保持一致
隔离性:事务执行过程中不受其他事务的干扰
持久性:事务结束,数据持久化到数据库
不考虑事务隔离引发的安全性问题
脏读
读到了其他事务正在操作但未提交的数据
A事务访问了一条数据X,并对X做了修改,B事务也访问了X,读取到了A事务修改后的X值,而A事务执行了回滚,X值恢复到了修改前,此时B事务之前读的X就成了脏数据
不可重复读
重复查询时,读到了其他事务更新的数据,导致重复查询结果不一致
A事务在执行过程中多此访问数据X,因为其他事务在多次读取的间隙时间内修改了X值,导致A事务多次读取到的X值不一致
幻读
读到了其他事务插入的数据,导致重复查询结果不一致
在一个事务中,先后两次进行读取相同的数据(一般是范围查询),但由于另一个事务新增或者删除了数据,导致前后两次结果不一致,幻读不可以理解成不可重复读的一种,应当单独理解,因为幻读会严重影响后续操作
事务隔离级别
未提交读 :脏读,不可重复读,虚读都有可能发生 1
已提交读 :避免脏读。但是不可重复读和虚读有可能发生 2
可重复读 :避免脏读和不可重复读.但是虚读有可能发生 4
串行化的 :避免以上所有读问题 8
Mysql默认是repeatable read,级别4
oracle默认是read committed,级别2
事务回滚
代码出现异常会执行事务回滚,事务整体失败
这里的异常指的是没有被处理的异常,一旦使用了try catch finally或者是throw&throws,事务就不会执行回滚
spring事务核心
PlatformTransactionManager平台事务管理器接口
spring本身不直接管理事务,而是提供多种事务管理器,针对不同持久化平台提供不同的实现类,如JDBCTransactionManager,HibernateTransactionManager,JpaTransactionManager等
TransactionDefinition事务管理的属性
transactionManager
通常一个项目只用一个事务管理器,但有些项目会包含多个互不相关的数据源,这时候就需要使用多个事务管理器,通过这个属性指定事务管理器
@Transactional(transactionManager = "txManager#singleton")
isolation,隔离级别
ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别
ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据
ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据
ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的
ISOLATION_SERIALIZABLE:所有事务逐个依次执行
未提交读:脏读,不可重复读,虚读都有可能发生 1
已提交读:避免脏读。但是不可重复读和虚读有可能发生 2
可重复读:避免脏读和不可重复读.但是虚读有可能发生 4
串行化的:避免以上所有读问题 8
propagation,传播行为
决定业务方法平行调用时,事务如何处理
PROPAGATION_REQUIRED required,支持当前事务,如果不存在 就新建一个(默认),最常用
A()调用B(),如果A没有开事务,那么会自动开启一个事务,如果A已经开了事务,就在这个事务中执行B,实际上99%的情况都是这种情况,相当于整体操作作为一个事务
PROPAGATION_SUPPORTS support,支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY mandatory,支持当前事务,如果不存在,抛出异常
下列配置可以保证平行方法没有在同一个事务中执行
PROPAGATION_REQUIRES_NEW requires_new,如果有事务存在,挂起当前事务,创建一个新的事务,这是为了在事务嵌套的情况下,内部事务不影响外部事务,其他事务
PROPAGATION_NOT_SUPPORTED not_supported,以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER never,以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED nested,如果当前事务存在,则嵌套事务执行0
readOnly,是否只读事务
如果一个事务中所有的数据库操作都是只读的,应该给该事务设置只读属性,帮助数据库引擎优化事务,提升数据库读写效率
true,false
timeout,超时设置
设置事务持续时间,超时强制回滚,避免一个事务对连接占用过长的时间
rollbaclFor,何种异常执行回滚
默认情况下Spring会为所有的运行时异常进行回滚
怎么使用spring事务管理
此处不讨论xml配置的方式,只关注注解式
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
简单demo
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, rollbaclFor = NullPointException)
public ...(){...}
//实际上也可以都使用默认配置,即
@Transactional
public ...(){...}
spring事务失效
数据库方面
1.数据库本身不支持事务,比如,mysql的myisam不支持事务,当然新版本的mysql已经移除了myisam引擎
spring方面
2.事务没有被spring管理,比如,在某个方法上使用了注解@Transactional,但是这个类没有被注入到spring容器中,那么这个事务其实是无效的
配置方面
3.数据源未配置事务管理器,未开启事务管理,配置transactionManager是开启spring事务管理的第一步,springboot无需手动开启,但xml配置的方式需要手动配置开启
4.事务传播性设置导致,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER,PROPAGATION_SUPPORTS有可能会导致事务失效
代码编写方面
5.方法不是public,@Transactional只有修饰public方法时才会生效,这是spring的明确要求
6.方法是final修饰,final方法无法被代理类重写,也就无法添加事务管理功能
7.同一个类中自身方法调用
比如,在类A的方法a上使用了注解@Transactional,在这个类A中有其他的方法b调用了这个方法a,那么a上标记的这个事务是无效的,因为spring事务管理是通过AOP实现的,而AOP是通过动态代理实现的,而同一个类中自身方法的调用是通过this,没有通过代理类,所以事务是无效的
解决方法:解决办法有多种
1.声明一个新的service,在service中调用这个@Transactional的方法
2.本类中注入自身
@Autowire
XxxService xxxService;
xxxService.do();
3.使用application.getBean()获取当前类对象,并调用目标方法
XxxService xxxService = applicationContext.getBean(XxxService.class);
xxxService.do();
4.使用AopContext,获取当前的代理对象并调用
((XxxService)AopContext.currentProxy()).do();
8.使用cglib代理,cglib代理对接口层的@Transactional是无效的
9.多线程调用,多个线程获取到的数据库连接不一样,则分别属于两个不同的事务(因为spring事务是基于数据库连接实现),显然无法一起提交和回滚
异常处理
10.rollbackFor异常指定错误,如果发生了rollbaclFor指定以外的异常,事务就不会rollback了
11.异常被catch了,如果必须要catch,那么手动做throw,抛出异常
通过springAOP实现自动事务,默认情况下不再需要对每个方法手动打注解
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* @description 通过AOP切面设置全局事务,拦截service包下面所有方法
* AOP术语:通知(Advice)、连接点(Joinpoint)、切入点(Pointcut)、切面(Aspect)、目标(Target)、代理(Proxy)、织入(Weaving)
*/
@Configuration
public class TransactionManagerConfig {
private static final int TX_METHOD_TIMEOUT = 5000;
/*定义切点变量*/
private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.web.service.impl.*.*(..))";
@Autowired
private PlatformTransactionManager transactionManager;
/**
* @description springBoot事务配置
*/
@Bean
public TransactionInterceptor TxAdvice() {
/*事务管理规则,声明具备事务管理的方法名*/
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
/*只读事物、不做更新删除等*/
/*当前存在事务就用当前的事务,当前不存在事务就创建一个新的事务*/
RuleBasedTransactionAttribute readOnlyRule = new RuleBasedTransactionAttribute();
/*设置当前事务是否为只读事务,true为只读*/
readOnlyRule.setReadOnly(true);
/* transactiondefinition 定义事务的隔离级别;
* PROPAGATION_NOT_SUPPORTED事务传播级别5,以非事务运行,如果当前存在事务,则把当前事务挂起*/
readOnlyRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
RuleBasedTransactionAttribute requireRule = new RuleBasedTransactionAttribute();
/*抛出异常后执行切点回滚*/
requireRule.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
/*PROPAGATION_REQUIRED:事务隔离性为1,若当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 */
requireRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
/*设置事务失效时间,如果超过5秒,则回滚事务*/
requireRule.setTimeout(TX_METHOD_TIMEOUT);
Map<String, TransactionAttribute> txMap = new HashMap<>();
txMap.put("add*", requireRule);
txMap.put("save*", requireRule);
txMap.put("insert*", requireRule);
txMap.put("update*", requireRule);
txMap.put("delete*", requireRule);
txMap.put("remove*", requireRule);
txMap.put("import*", requireRule);
txMap.put("get*", readOnlyRule);
txMap.put("query*", readOnlyRule);
txMap.put("find*", readOnlyRule);
txMap.put("select*", readOnlyRule);
source.setNameMap(txMap);
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
return txAdvice;
}
/**
* @description 利用AspectJExpressionPointcut设置切面=切点+通知(写成内部bean的方式)
*/
@Bean
public Advisor txAdviceAdvisor() {
/* 声明切点的面
* 切面(Aspect):切面就是通知和切入点的结合。通知和切入点共同定义了关于切面的全部内容——它的功能、在何时和何地完成其功能。
* */
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
/*声明和设置需要拦截的方法,用切点语言描写*/
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
/*设置切面=切点pointcut+通知TxAdvice*/
return new DefaultPointcutAdvisor(pointcut, TxAdvice());
}
}
spring中运用的设计模式
工厂设计模式:Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象
代理设计模式:Spring AOP 功能的实现
单例设计模式:Spring 中的 Bean 默认都是单例的
模板方法模式:Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式
包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源
观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用
适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller