5. Spring、Mybatis整合Service层事务控制优化思路分析
# spring中处理事务的两种方式
1. 编程式事务处理
定义:通过在业务层中注入事务管理器对象,然后通过编码的方式进行事务控制
缺点:
1. 代码冗余
2. 不够通用
3. 不便于维护
2. 声明式事务处理 [推荐]
定义:通过利用aop切面编程进行事务控制 并对事务属性在配置文件中完成细粒度配置 这种方式 称之为声明事务
好处:
通用 减少代码冗余 更加专注于业务逻辑开发 无需重复编码
# spring、mybatis整合开发之Service层事务优化
1. 自定义完成事务管理
a. 开发基于事务的通知 环绕通知
TransactionAdvice implements MethodInterceptor{
private PlatformTransactionManager ts; set;
public Object invoke(MethodInvocation mi) throws Throwable {
// 创建事务配置对象
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
// 获取事务状态
TransactionStatus status = ts.getTransaction(transactionDefinition);
// 放行目标方法
try{
Object result = mi.proceed(); // 放行
ts.commit(status); // 提交事务
return result;
}catch(Expection e){
ts.rollback(status);
}
}
}
b. 配置切面
1). 配置通知对象
<bean class="xxx.TransactionAdvice" id="tx">
<property name="ts" ref="dataSourceTransactionManager"/>
</bean>
2). 配置切面
<aop:config>
<aop:pointcut id="pc" expression="within(com.baizhi.service.*SerbiceImpl)"/>
<aop:advisor advice-ref="tx" pointcut-ref="pc"/>
</aop:config>
2. Spring框架开发声明式事务编程
a. spring框架提供 tx:advice 标签
作用:
1. 可以根据事务管理器创建一个基于事务环绕通知对象
2. tx:advice标签可以对事务进行细粒度控制(不使用bean标签的原因是tx:advice不仅可以在工厂中创建环绕通知对象,还可 以进行细粒度的控制)
// 创建出来的通知对象在工厂中唯一标识 // 这个transactionManager类似于ref,传入id
<tx:advice id="transactionAdvice" transactionManager="事务管理器是谁">
<!--事务细粒度控制:基于方法层面-->
<tx:attributes>
<tx:method name="save"/>
<!--<tx:method name="save*,支持通配符"/>-->
<!--<tx:method name="delete,以后还要对其他方法进行事务控制,再在attributes里面写就行了"/>-->
</tx:attributes>
</tx:advice>
</tx:advice>
b. 配置切面
<aop:config>
<aop:pointcut id="pc" expression="within(com.baizhi.service.*SerbiceImpl)"/>
<aop:advisor advice-ref="tx" pointcut-ref="pc"/>
</aop:config>
在上一篇博客我们可以看到,在Service层进行事务控制时,需要在声明一个事务管理器成员变量,而且在使用时还要传入事务状态特别麻烦,并且在进行增加、删除、修改时都要进行这样的操作,在一定程度上造成了代码的冗余。我们可以联系到前面学过的通知的知识,我们可以自定义一个环绕通知进行事务控制,并在配置文件中注册,之后装配切面就可以了,相关代码如下:
- 自定义环绕通知类:
// 自定义环绕通知类必须实现系统定义的环绕通知接口
TransactionAdviceimplementsMethodInterceptor{private PlatformTransactionManager ts; set;public Objectinvoke(MethodInvocation mi)throws Throwable{// 创建事务配置对象
TransactionDefinition transactionDefinition=newDefaultTransactionDefinition();// 获取事务状态
TransactionStatus status= ts.getTransaction(transactionDefinition);// 放行目标方法try{
Object result= mi.proceed();// 放行
ts.commit(status);// 提交事务return result;}catch(Expection e){
ts.rollback(status);}}}
- 配置文件:
<!--1). 配置通知对象--><beanclass="xxx.TransactionAdvice"id="tx"><propertyname="ts"ref="dataSourceTransactionManager"/></bean><!--2). 配置切面--><aop:config><aop:pointcutid="pc"expression="within(com.baizhi.service.*SerbiceImpl)"/><aop:advisoradvice-ref="tx"pointcut-ref="pc"/></aop:config>
利用环绕通知可以解决以前我们在进行事务控制时所产生的许多问题。由于自定义环绕通知这段代码无论谁来写都是一样的,所以框架提供了官方类,使用这个官方类就可以起到和上面自定义环绕通知进行事务控制一样的作用,而且非常方便,这就是spring框架开发声明式事务编程。
在声明式事务编程中我们需要使用tx:advice标签(把tx:advice里面的内容就是一个环绕通知)在工厂中注册官方创建的环绕通知进行事务管理,之后再配置切面就可以了,非常方便。
- 配置文件
<!--配置文件中部分内容--><!--数据源管理器--><!--它是用来控制数据源的线程安全问题,它不生产数据源,所以要告诉它控制哪个数据源的线程安全问题,所以要注入数据源--><beanclass="org.springframework.jdbc.datasource.DataSourceTransactionManager"id="transactionManager"><!--注入数据源对象--><propertyname="dataSource"ref="dataSource"/></bean><!--tx:advice标签
id:基于事务管理器创建的环绕通知对象在工厂中的唯一标识
作用:
1. 根据指定的事务管理器在工厂中创建一个事务的环绕通知对象
2. 对业务层方法进行细粒度事务控制
--><!--注意:因为我们在自己创建环绕通知时使用了事务管理器成员变量,所以在使用官方的环绕通知管理实务操作时
工厂中必须有事务管理器对象--><!--使用tx:advice标签相当于在工厂中注册了一个用于事务管理的环绕通知,可以看成是一个环绕通知--><!--id:表示这个环绕通知在工厂中的唯一id --><!--transaction-manager:传入事务管理对象在工厂中的唯一id--><tx:adviceid="txAdvice"transaction-manager="transactionManager"><!--事务细粒度控制:基于方法层面--><!--如果tx:advice标签内部没有attributes进行细粒度的方法管理,实务操作是不会生效的,也就是说tx:advice内部
必须使用attributes进行方法细粒度的管理--><tx:attributes><tx:methodname="save"/><!-- <tx:method name="save*"/>--><!-- <tx:method name="delete"/>--></tx:attributes></tx:advice><!--配置事务切面--><aop:config><!--配置切入点--><aop:pointcutid="pc"expression="within(com.baizhi.service.*ServiceImpl)"/><!--组装切面--><aop:advisoradvice-ref="txAdvice"pointcut-ref="pc"/></aop:config>
<!--配置文件中全部内容--><?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--创建DataSource--><beanclass="com.alibaba.druid.pool.DruidDataSource"id="dataSource"><propertyname="driverClassName"value="com.mysql.jdbc.Driver"/><propertyname="url"value="jdbc:mysql://localhost:3306/lb"/><propertyname="username"value="root"/><propertyname="password"value="root"/></bean><!--创建sqlSessionFactory--><beanclass="org.mybatis.spring.SqlSessionFactoryBean"id="sqlSessionFactory"><!--因为使用了mybatis官方的创建SqlSessionFactory对象的工具类,所以不能使用mybatis的主配置文件了,
主配置文件最重要的是数据源+mapper,所以我们要在这里写上数据源和mapper配置--><!--依赖数据源--><propertyname="dataSource"ref="dataSource"/><!--注册mapper配置文件--><propertyname="mapperLocations"><array><value>classpath:com/baizhi/mapper/UserDAOMapper.xml</value></array></property><!--注入别名相关配置 typeAliasesPackage:用来给指定包中所有类起别名 默认的别名:类名|类名首字母小写--><propertyname="typeAliasesPackage"value="com.baizhi.eneity"/></bean><!--一次性创建项目中所有DAO对象 MapperScannerConfigurer
MapperScannerConfigurer:
默认创建对象在工厂中唯一标识:接口的首字母小写的名字
UserDAO=====> userDAO Userdao====> userdao
OrderDAO====> orderDAO Orderdao====> orderdao
EmpDAO====> empDAO
--><beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer"><!--注入SqlSessionFactory--><propertyname="sqlSessionFactoryBeanName"value="sqlSessionFactory"/><!--扫描DAO接口所在的包--><propertyname="basePackage"value="com.baizhi.dao"/></bean><!--管理Service组件--><beanclass="com.baizhi.service.UserServiceImpl"name="userService"><propertyname="userDAO"ref="userDAO"/></bean><!--数据源管理器--><!--它是用来控制数据源的线程安全问题,它不生产数据源,所以要告诉它控制哪个数据源的线程安全问题,所以要注入数据源--><beanclass="org.springframework.jdbc.datasource.DataSourceTransactionManager"id="transactionManager"><!--注入数据源对象--><propertyname="dataSource"ref="dataSource"/></bean><!--tx:advice标签
id:基于事务管理器创建的环绕通知对象在工厂中的唯一标识
作用:
1. 根据指定的事务管理器在工厂中创建一个事务的环绕通知对象
2. 对业务层方法进行细粒度事务控制
--><tx:adviceid="txAdvice"transaction-manager="transactionManager"><!--事务细粒度控制:基于方法层面--><tx:attributes><tx:methodname="save"/><!-- <tx:method name="save*"/>--><!-- <tx:method name="delete"/>--></tx:attributes></tx:advice><!--配置事务切面--><aop:config><aop:pointcutid="pc"expression="within(com.baizhi.service.*ServiceImpl)"/><aop:advisoradvice-ref="txAdvice"pointcut-ref="pc"/></aop:config></beans>
注:这里因为是对上一篇的优化,所以大部分是一样的,这里只是把一下不一样的粘出来了
还有,不要使用错了的包的advice,要用tx包下的tx:advice标签
- UserServiceImpl类
publicclassUserServiceImplimplementsUserService{private UserDAO userDAO;publicvoidsetUserDAO(UserDAO userDAO){this.userDAO= userDAO;}@Overridepublic List<User>findAll(){// 查询操作不需要事务控制return userDAO.findAll();}@Overridepublicvoidsave(User user){
userDAO.save(user);//int i = 1 / 0;}}
- 测试和结果
测试之前的表
测试
publicclassTestUserService{publicstaticvoidmain(String[] args){
ApplicationContext context=newClassPathXmlApplicationContext("spring.xml");
UserService userService=(UserService) context.getBean("userService");//System.out.println(userService.getClass());// save// 使用数据库自动生成的主键
userService.save(newUser(null,"百知教育",23,newDate()));// findAll
userService.findAll().forEach(user-> System.out.println("user = "+ user));}}
有异常时:
查看数据库中的表:
可以看到有异常时事务回滚了
我们再来看看没有异常时的执行结果:
执行之后表中记录:
我们可以看到,我们成功的使用官方创建的用于事务管理的环绕通知对事务进行了成功的管理。
6. Spring、Mybatis整合之最终编码
所有需要用到的类以及包结构:
步骤:
- 引入依赖
- 建表
- 实体类
- DAO接口
- Mapper配置文件
- Service接口
- Service实现类
- 编写Spring、Mybatis整合配置Spring.xml文件
- 测试Service
- 在pom.xml中引入依赖
<!--spring核心及相关依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>4.3.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>4.3.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>4.3.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>4.3.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>4.3.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>4.3.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.3.2.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>4.3.2.RELEASE</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version></dependency><!--mybatis--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.4</version></dependency><!--mybatis-spring--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.4</version></dependency><!--druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.19</version></dependency>
- 数据库中表
- 建实体类
publicclassUser{private String id;private String name;private Integer age;private Date bir;publicUser(){}publicUser(String id, String name, Integer age, Date bir){this.id= id;this.name= name;this.age= age;this.bir= bir;}public StringgetId(){return id;}publicvoidsetId(String id){this.id= id;}public StringgetName