本文已参与「新人创作礼」活动,一起开启掘金创作之路。
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层进行事务控制时,需要在声明一个事务管理器成员变量,而且在使用时还要传入事务状态特别麻烦,并且在进行增加、删除、修改时都要进行这样的操作,在一定程度上造成了代码的冗余。我们可以联系到前面学过的通知的知识,我们可以自定义一个环绕通知进行事务控制,并在配置文件中注册,之后装配切面就可以了,相关代码如下:
- 自定义环绕通知类:
// 自定义环绕通知类必须实现系统定义的环绕通知接口
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);
}
}
}
- 配置文件:
<!--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>
利用环绕通知可以解决以前我们在进行事务控制时所产生的许多问题。由于自定义环绕通知这段代码无论谁来写都是一样的,所以框架提供了官方类,使用这个官方类就可以起到和上面自定义环绕通知进行事务控制一样的作用,而且非常方便,这就是spring框架开发声明式事务编程。
在声明式事务编程中我们需要使用tx:advice标签(把tx:advice里面的内容就是一个环绕通知)在工厂中注册官方创建的环绕通知进行事务管理,之后再配置切面就可以了,非常方便。
- 配置文件
<!--配置文件中部分内容-->
<!--数据源管理器-->
<!--它是用来控制数据源的线程安全问题,它不生产数据源,所以要告诉它控制哪个数据源的线程安全问题,所以要注入数据源-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!--注入数据源对象-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--tx:advice标签
id:基于事务管理器创建的环绕通知对象在工厂中的唯一标识
作用:
1. 根据指定的事务管理器在工厂中创建一个事务的环绕通知对象
2. 对业务层方法进行细粒度事务控制
-->
<!--注意:因为我们在自己创建环绕通知时使用了事务管理器成员变量,所以在使用官方的环绕通知管理实务操作时
工厂中必须有事务管理器对象-->
<!--使用tx:advice标签相当于在工厂中注册了一个用于事务管理的环绕通知,可以看成是一个环绕通知-->
<!--id:表示这个环绕通知在工厂中的唯一id -->
<!--transaction-manager:传入事务管理对象在工厂中的唯一id-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--事务细粒度控制:基于方法层面-->
<!--如果tx:advice标签内部没有attributes进行细粒度的方法管理,实务操作是不会生效的,也就是说tx:advice内部
必须使用attributes进行方法细粒度的管理-->
<tx:attributes>
<tx:method name="save"/>
<!-- <tx:method name="save*"/>-->
<!-- <tx:method name="delete"/>-->
</tx:attributes>
</tx:advice>
<!--配置事务切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pc" expression="within(com.baizhi.service.*ServiceImpl)"/>
<!--组装切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
<!--配置文件中全部内容-->
<?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: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-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/lb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--创建sqlSessionFactory-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<!--因为使用了mybatis官方的创建SqlSessionFactory对象的工具类,所以不能使用mybatis的主配置文件了,
主配置文件最重要的是数据源+mapper,所以我们要在这里写上数据源和mapper配置-->
<!--依赖数据源-->
<property name="dataSource" ref="dataSource"/>
<!--注册mapper配置文件-->
<property name="mapperLocations">
<array>
<value>classpath:com/baizhi/mapper/UserDAOMapper.xml</value>
</array>
</property>
<!--注入别名相关配置 typeAliasesPackage:用来给指定包中所有类起别名 默认的别名:类名|类名首字母小写-->
<property name="typeAliasesPackage" value="com.baizhi.eneity"/>
</bean>
<!--一次性创建项目中所有DAO对象 MapperScannerConfigurer
MapperScannerConfigurer:
默认创建对象在工厂中唯一标识:接口的首字母小写的名字
UserDAO=====> userDAO Userdao====> userdao
OrderDAO====> orderDAO Orderdao====> orderdao
EmpDAO====> empDAO
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入SqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--扫描DAO接口所在的包-->
<property name="basePackage" value="com.baizhi.dao"/>
</bean>
<!--管理Service组件-->
<bean class="com.baizhi.service.UserServiceImpl" name="userService">
<property name="userDAO" ref="userDAO"/>
</bean>
<!--数据源管理器-->
<!--它是用来控制数据源的线程安全问题,它不生产数据源,所以要告诉它控制哪个数据源的线程安全问题,所以要注入数据源-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!--注入数据源对象-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--tx:advice标签
id:基于事务管理器创建的环绕通知对象在工厂中的唯一标识
作用:
1. 根据指定的事务管理器在工厂中创建一个事务的环绕通知对象
2. 对业务层方法进行细粒度事务控制
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--事务细粒度控制:基于方法层面-->
<tx:attributes>
<tx:method name="save"/>
<!-- <tx:method name="save*"/>-->
<!-- <tx:method name="delete"/>-->
</tx:attributes>
</tx:advice>
<!--配置事务切面-->
<aop:config>
<aop:pointcut id="pc" expression="within(com.baizhi.service.*ServiceImpl)"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
</beans>
注:这里因为是对上一篇的优化,所以大部分是一样的,这里只是把一下不一样的粘出来了
还有,不要使用错了的包的advice,要用tx包下的tx:advice标签
- UserServiceImpl类
public class UserServiceImpl implements UserService{
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public List<User> findAll() {
// 查询操作不需要事务控制
return userDAO.findAll();
}
@Override
public void save(User user) {
userDAO.save(user);
//int i = 1 / 0;
}
}
- 测试和结果
测试之前的表
测试
public class TestUserService {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
//System.out.println(userService.getClass());
// save
// 使用数据库自动生成的主键
userService.save(new User(null, "百知教育", 23, new Date()));
// 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>
- 数据库中表
- 建实体类
public class User {
private String id;
private String name;
private Integer age;
private Date bir;
public User() {
}
public User(String id, String name, Integer age, Date bir) {
this.id = id;
this.name = name;
this.age = age;
this.bir = bir;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBir() {
return bir;
}
public void setBir(Date bir) {
this.bir = bir;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
", bir=" + bir +
'}';
}
}
- DAO接口
public interface UserDAO {
void save(User user);
List<User> findAll();
}
- Mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.baizhi.dao.UserDAO">
<insert id="save" parameterType="User">
insert into t_user values(#{id}, #{name}, #{age}, #{bir})
</insert>
<select id="findAll" resultType="com.baizhi.eneity.User">
select id, name, age, bir from t_user
</select>
</mapper>
- Service接口
public interface UserService {
void save(User user);
List<User> findAll();
}
- Service实现类
public class UserServiceImpl implements UserService{
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
public void save(User user) {
// 处理业务逻辑
userDAO.save(user);
}
public List<User> findAll() {
return userDAO.findAll();
}
}
- 编写Spring、Mybatis整合配置Spring.xml文件
步骤:
- 创建数据源
- 创建sqlSessionFactoy
- 创建DAO
- 创建事务管理器
- 创建事务环绕通知并进行事务细粒度控制
- 配置事务切面
- 管理Service层组件
<?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: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">
<!--创建数据源-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/lb?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--创建sqlSessionFactoy-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<!--注入datasource mapperLocations typeAliasesPackage-->
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:com/baizhi/mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.baizhi.eneity"/>
</bean>
<!--创建DAO-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sqlSessionFactory dao接口所在包-->
<!--value要的是sqlSessionFactory在工厂中的名字-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.baizhi.dao"/>
</bean>
<!--创建事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--创建事务环绕通知并进行事务细粒度控制-->
<!--transaction-manager找ref -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
</tx:attributes>
</tx:advice>
<!--配置事务切面-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.baizhi.service.*ServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
<!--管理Service层组件-->
<bean class="com.baizhi.service.UserServiceImpl" id="userService">
<property name="userDAO" ref="userDAO"/>
</bean>
</beans>
- 测试
public class TestUserService {
private ClassPathXmlApplicationContext context;
@Before
public void before(){
this.context = new ClassPathXmlApplicationContext("spring.xml");
}
@Test
public void testSave(){
UserService userService = (UserService) context.getBean("userService");
User user = new User();
user.setBir(new Date());
user.setName("小陈");
user.setAge(23);
userService.save(user);
}
@Test
public void testFindAll(){
UserService userService = (UserService) context.getBean("userService");
userService.findAll().forEach(user -> {
System.out.println("user = " + user);
});
}
@After
public void after(){
context.close();
}
}
7. log4j日志使用
# spring、mybatis整合中使用log4j
1. log4j
作用:用来展示项目中的运行日志
日志分类:
1. 项目根日志 (全局日志) [因为是全局日志,日志内容非常多,一般用不到]
2. 项目子日志 (指定包级别日志) [一般用的就是这种日志]
日志级别:
错误 警告 信息
ERROR(高) > WARN > INFO > DEBUG(低) [一般在做日志调试的时候都设置为DEBUG]
ERROR:当项目错误时才会展示的日志
级别越低反而输出的信息越多
2. 如何使用
a). 引入依赖
<!--log4j-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
b). 引入log4.properties配置文件
注意:必须放置在resources根目录下 名字叫 log4j.properties
log4j.rootLogger = ERROR,bb
log4j.appender.bb = org.apache.log4j.ConsoleAppender
log4j.appender.bb.layout = org.apache.log4j.PatternLayout
log4j.appender.bb.layout.conversionPattern = [%p] %d{yyyy-MM-dd} %m%n
log4j.logger.com.baizhi.dao = DEBUG
log4j.logger.org.springframework = ERROR
log4j日志的配置:
# root logger
# gen ri zhi de ji bie: ERROR,jiu shi dang kuang jia zhong chu xian cuo wu shi cai hui zhan shi, bb dai biao bian liang ming zi
log4j.rootLogger=ERROR,bb
# da dao kong zhi tai
log4j.appender.bb=org.apache.log4j.ConsoleAppender
# bu jv, shi yong zi ding yi de ge shi
log4j.appender.bb.layout=org.apache.log4j.PatternLayout
# zi ding yi de jv ti ge shi jv ti shi shen mo
# %p: dai biao shu chu ji bie
# %d: dai biao shu chu ri zhi de da yin shi jian
# %m: dai biao shu chu zhu xian cheng yun xing ri zhi
# %n: dai biao shu chu wan zhe hang ri zhi hui che huan hang
log4j.appender.bb.layout.conversionPattern=[%p] %d{yyyy-MM-dd} %m%n
# package logger
# jian kong com.baizhi.dao bao xia de ri zhi
# zhan shi dao de ri zhi
log4j.logger.com.baizhi.dao=DEBUG
# zhan shi spring kuang jia de ri zhi
log4j.logger.org.springframework=ERROR
实际效果: