摘要:面试时常常被面试官问到这个问题。Spring框架中的核心技术:控制反转/依赖注入/面向切面编程/Spring的声明式事务/以及Spring生态相关产品的简介,本文是Spring第六讲:Spring事务原理及应用
如下示例:
public void update(Integer id, String name, Integer age, Integer marks, Integer year) {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
String SQL1 = "update Student set age = ?,name = ? where id = ?";
jdbcTemplateObject.update(SQL1, age,name, id);
System.out.println("Updated Record with ID = " + id);
String SQL2 = "update Marks set marks = ?,year = ? where sid = ?";
jdbcTemplateObject.update(SQL2, marks,year, id);
System.out.println("Updated Record with SID = " + id);
transactionManager.commit(status);
} catch (DataAccessException e) {
System.out.println("Error in creating record, rolling back");
transactionManager.rollback(status);
throw e;
}
return;
}
它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式
基于TX和AOP的xml配置文件方式,已经过时
步骤:
1、配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="sessionFactory" ref="sessionFactory">
<bean/>
2、配置注解事务
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--配置advice通知 ->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
<tx:method name="*" propagation="SUPPORTS"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="interceptorPointCuts"
expression="execution(* cn.gov.zcy.service.*.manager.*.*(..))” id="pointcut"/>
<aop:advisor advice-ref="txAdvice"
pointcut-ref="interceptorPointCuts"/>
aop:config>
execution 语法结构
execution([权限修饰符][返回类型][类全路径]方法名称)
execution(com.bluesky.spring.dao..*(…)) 所有方法进行增强
基于@Transactional 注解
spring事务配置
TransactionManager----datasourcetransactionManager // jdbc配置
----hibernatetransactionManager // hibernate配置
代理机制----bean和代理 --每个bean有一个代理
–所有bean共享一个代理基类
----使用拦截器
----使用tx标签配置的拦截器
----全注解配置
基于TransactionInterceptor的声明式事务管理:
代码示例如下
@Transactional
public class AccountServiceImpl implements AccountService{}
代码示例如下:
@Transactional
@RestController
@RequestMapping
publicclass MybatisPlusController {
@Autowired
private CityInfoDictMapper cityInfoDictMapper;
@Transactional(rollbackFor = Exception.class)
@GetMapping("/test")
public String test() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setParentCityId(2);
cityInfoDict.setCityName("2");
cityInfoDict.setCityLevel("2");
cityInfoDict.setCityCode("2");
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert + "";
}
}
Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:
int getPropagationBehavior():事务的传播行为:就是多个事务方法相互调用时,事务如何在这些方法间传播
int getIsolationLevel():事务的隔离级别
int getTimeout():事务的过期时间
boolean isReadOnly():事务的读写特性。
传播行为是解决什么问题呢?
propagation属性(共7种)
isolation 属性
timeout 属性
readOnly 属性
rollbackFor 属性
noRollbackFor属性
1、TransactionProxyFactoryBean
2、TransactionAttributeSourceAdvisor
3、TransactionInterceptor
4、TransactionAspectSupport
5、AbstractPlatformTransactionManager
6、TransactionStatus
7、TransactionInfo
AOP原理:
Advisor最重要的两个部分:PointCut和Advice属性。
Join Point:它是Aspect可以切入的特定点,在Spring里面只有方法可以作为Join Point(是可利用的机会,具体由pointcut指定)
Advice:它定义了切面中能够采取的动作。如果去看Spring源码,就会发现Advice、Join Point并没有定义在Spring自己的命名空间里,这是因为他们是源自AOP联盟,可以看作是Java工程师在AOP层面沟通的通用规范
分别解释这三个类:
总结:Spring支持AspectJ的注解式切面编程
1、使用@Aspect声明是一个切面;
2、使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数;
3、其中@After、@Before、@Around参数的拦截规则为切点(PointCut),为了使切点复用,可使用@PointCut专门定义拦截规则;
4、其中符合条件的每一个被拦截处为连接点(JointPoint)
示例:
1、添加Spring AOP支持及AsectJ依赖
2、编写拦截规则的注解(编写注解)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action{
String name();//注解是一种元数据,即解释数据的数据
}
3、编写使用注解的被拦截类(使用注解)
@Service
public class DemoAnnotationService{
@Action(name="拦截式拦截的add操作")
public void add(){}
}
4、编写切面
@Aspect //切面
@Component //让此切面加入IOC容器
public class LogAspect{
@PointCut("@annotation(com.wisely.aop.Action)")
public void annotationPointCut(){};
//后置处理逻辑,在切入点方法执行后执行
@After("anntationPointCut")
public void after(JointPoint jointPoint){
MethodSignature signature=(MethodSignature)jointPoint.getSignature();
Method method =getSignature.getMethod();
Action action = method.getAnnotation(Action.class);
sout("Q"+action.name());
}
}
5、配置类
6、运行
Demo示例如下:
@Configuration
public class TransactionManagerConfiguration {
/**
* 配置RDS数据源事务管理器
* @return
*/
@Bean(name = "drdsTransactionManager")
public PlatformTransactionManager drdsTransactionManager(DynamicDataSource dynamicDataSource){
DataSource targetDataSourceByUserDefinedKey = dynamicDataSource.getTargetDataSourceByUserDefinedKey("drds");
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(targetDataSourceByUserDefinedKey);
return dataSourceTransactionManager;
}
@Bean(name = "drdsSqlSessionFactory")
public SqlSessionFactory drdsSqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
DataSource targetDataSourceByUserDefinedKey = dynamicDataSource.getTargetDataSourceByUserDefinedKey("drds");
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(targetDataSourceByUserDefinedKey);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/itemMapper.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("cn.gov.zcy.service.item.domain");
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "drdsSqlSessionTemplate")
public SqlSessionTemplate drdsSqlSessionTemplate(@Qualifier("drdsSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
return sqlSessionTemplate;
}
}
见这篇文章:MySQL第七讲:数据库事务及MVCC机制/分布式事务实战
使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。
2、@Transactional 注解属性 propagation 设置错误
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
3、@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
private int getDepth(Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(this.exceptionName)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass == Throwable.class) {
return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}
那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
/**
* B 插入字段为 3的数据
*/
this.insertB();
/**
* A 插入字段为 2的数据
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("3");
cityInfoDict.setParentCityId(3);
return cityInfoDictMapper.insert(cityInfoDict);
}
解决方案
ApplicationContextUtil
来管理,使得 Spring容器能管理事务。AgGoodsManager agGoodsManager = (AgGoodsManager) ApplicationContextUtil.getBean("agGoodsManager");
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插入字段为 2的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插入字段为 3的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?
答案:不能!
会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。
spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候 try catch反倒会画蛇添足。
6、数据库引擎不支持事务
这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。
吃得苦中苦,方为人上人