Spring boot 整合mybatis已经配置好了,这里就不重新叙述,只是挑选出几个和事务有关的地方。
首先做一些准备,开起Spring boot 的事务比较简单,只是需要spring-tx-4.3.6.RELEASE.jar这个包,这里看自己项目的版本下载的jar。
这个包也不用专门去pom配置,在我们配置Spring boot的时候就已经下载了。Spring 开起注解事务只需要在启动类上配置@EnableTransactionManagement,启动注解事务,等同于传统Spring 项目中xml配置<tx:annotation-driven />
package com.wzh.application;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Spring Boot 应用启动类,这里继承SpringBootServletInitializer并重写SpringApplicationBuilder方法
* 是为了打包为war进行外部tomcat的部署
*/
@SpringBootApplication // Spring Boot 应用的标识
@EnableTransactionManagement // 启动注解事务,等同于传统Spring 项目中xml配置<tx:annotation-driven />
@ComponentScan(basePackages = { "com.wzh"}) // 指定spring管理路径,就是那些bean 注解的路径
@MapperScan({ "com.wzh.**.mapper" }) // mapper 接口类扫描包配置,两个*为目录通配符
public class Application extends SpringBootServletInitializer{
// 程序启动入口
public static void main(String[] args) {
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
// 创建数据源,因为用是mybatis-spring 1.2 取消了数据源的自动注入,所以这里需要手动配置
@Bean
@ConfigurationProperties(prefix = "spring.datasource")// 指定数据源的前缀 ,在application.properties文件中指定
public DataSource dataSource() {
return new DataSource();
}
// 创建SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
//解决myBatis下 不能嵌套jar文件的问题
VFS.addImplClass(SpringBootVFS.class);
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 两个*为目录通配符
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*.xml"));
//指定扫描别名包的路径,多个bean的扫描路径,拼接以分号隔开
String typeAliasesPackage = "com.wzh.demo.domain;";
sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
return sqlSessionFactoryBean.getObject();
}
// 创建事物管理器
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
下面来写一个junit的启动基类,并启动测试一下
import com.wzh.application.Application;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* junit 测试基类
*/
//SpringJUnit支持,由此引入Spring-Test框架支持!
@RunWith(SpringJUnit4ClassRunner.class)
//启动类
@SpringBootTest(classes = Application.class)
//web项目支持
@WebAppConfiguration
public class BaseJunit {
@Test
public void runJunitTest()
{
System.out.println("Junit 启动测试");
}
}
显示Junit是配置好了,下面我们来进行事务的配置,因为SpringBoot中已经支持事务,不需额外配置,这里的配置是简单的介绍下事务的隔离等级和常用的参数
spring事务特性
spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口
其中TransactionDefinition接口定义以下特性:
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务在Spring中的有两种,编程事务和注解事务,先说下@Transactional的参数
参 数 名 称 | 功 能 描 述 |
---|---|
readOnly | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor | 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) |
noRollbackFor | 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) |
propagation | 该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout | 该属性用于设置事务的超时秒数,默认值为-1表示永不超时 |
注解事务测试
@Override
@Transactional
public int addUser(UserBean user) {
//这里做注解事务的测试,调用两次add方法,然后报错一次,看事务是否成功
int a1 = userDao.addUser(user);
Integer.valueOf("ABC");
int a2 = userDao.addUser(user);
return (a1 + a2);
}
这里用的默认的事务等级,出错就整个回滚,可以看到在第一个插入语句后故意调用方法出现转换错误,测试回滚成功。
声明式事务除了注解声明,还可以显示的编程声明,编程声明会加大代码的耦合程度,不过在某些场合下又不得不用,这里就看项目的实际情况定了。
编程事务
package com.wzh.demo.service.impl;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.wzh.demo.dao.UserDao;
import com.wzh.demo.domain.UserBean;
import com.wzh.demo.service.IUserService;
@Service("userService")
public class UserServiceImpl implements IUserService
{
/**
* 编程事务对象
*/
private final TransactionTemplate transactionTemplate;
/**
* 构造方法初始化事务模板
*/
public UserServiceImpl(@Qualifier("transactionManager") PlatformTransactionManager transactionManager)
{
this.transactionTemplate = new TransactionTemplate(transactionManager);
//设置事务等级
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
//设置连接超时时间 30seconds
this.transactionTemplate.setTimeout(30);
}
@Autowired
@Qualifier(value="userDao")
private UserDao userDao;
@Override
public int addUserByProgramming(final UserBean user) {
//开启编程事务
Integer i = (Integer) transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
try {
int a1 = userDao.addUser(user);
Integer.valueOf("ABC");
int a2 = userDao.addUser(user);
return (a1 + a2);
} catch (Exception e) {
//异常回滚
status.setRollbackOnly();
e.printStackTrace();
}
return 0;
}
});
return i;
}
}
junit测试
package userTest;
import base.BaseJunit;
import com.wzh.demo.dao.UserDao;
import com.wzh.demo.domain.UserBean;
import com.wzh.demo.service.IUserService;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* user表测试类
*/
public class userTest01 extends BaseJunit{
@Autowired
@Qualifier(value = "userDao")
private UserDao userDao;
@Autowired
@Qualifier(value = "userService")
private IUserService userService;
/**
* 新增测试
*/
@Test
public void testAddUser()
{
UserBean bean = new UserBean();
bean.setName("编程事务测试");
bean.setSex("男");
bean.setAge(33L);
int i = userService.addUserByProgramming(bean);
Assert.assertSame(2,i);
}
}