Spring与Mybatis整合事务管理
Posted 全栈攻城狮之道
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring与Mybatis整合事务管理相关的知识,希望对你有一定的参考价值。
本篇文章通过搭建一个Spring整合Mybatis的小项目,带你理解Spring与Mybatis整合的整个流程,重点还会介绍Spring的事务管理。这应该也是ssm框架整合最难理解的一步了,只要整合了sm后面再套个表现层就可以了。
maven项目添加依赖
首先使用maven创建一个java空项目,然后根据需要配置pom.xml文件来导入依赖的jar包。至于我为什么要选择maven项目来讲解,是因为要导入的jar包太多,我的mac电脑硬盘有限,如果每个小例子都要copy一份jar包,那我的硬盘也会所剩无几了。
先例举出项目需要依赖的jar包,既然是spring整合mybatis那自然是少不了mybatis的核心jar包和spring的核心jar包、ioc的jar包、aop的jar包、context的jar包,因为要用到事务管理,所以还需要添加spring-tx事务管理jar包和spring-jdbc包。此外,将mybatis整合到spring中还需要一个额外的mybatis-spring.jar包,要使用c3p0数据库连接池就需要c3p0.jar包,还有访问数据库不可缺失的数据库驱动工具包,这里使用mysql,所以是mysql-connector-java.jar包。
一些额外的需要:
junit:单元测试的jar包;
spring-test:spring提供的编写单元测试的框架;
aspectjweaver:AspectJ是一种编译期的用注解形式实现的aop,spring整合了AspectJ,在spring体系中可以使用aspectj语法来实现aop;
log4j:spring和mybatis打印日记依赖的工具包;
附pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>wjy.sm</groupId>
<artifactId>SpringMyBatisMavenDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<!-- spring版本 -->
<spring.version>4.3.9.RELEASE</spring.version>
<!--aspectj版本-->
<aspectj.version>1.9.1</aspectj.version>
<!-- mybatis版本 -->
<mybatis.version>3.2.6</mybatis.version>
<!-- mybatis-spring版本 -->
<mybatis-spring.version>1.3.1</mybatis-spring.version>
<!-- c3p0版本号 -->
<c3p0.version>0.9.5.2</c3p0.version>
<!-- junit版本 -->
<junit.version>4.12</junit.version>
</properties>
<!--添加依赖包-->
<dependencies>
<!-- spring框架核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 切面 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring-tx,配置事务管理需要 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring单元测试框架,需要依赖junit -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- mybatis包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- mybatis-spring包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- c3p0连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- log4j打印日记、输出日记到文件 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
spring整合mybatis的配置
spring整合mybatis不需要在mybatis的核心配置文件中配置环境信息了,也不需要配置sql映射文件部分了,这些配置都交由spring来配置管理。
在resource目录下新建一个mybatis-config.xml文件,该文件作为mybatis的核心配置文件,只需要配置一些setting标签,比如配置日记打印来查看sql语句的输出。
[mybatis-config.xml]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 打印日记 -->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
创建数据库和表(tb_user),编写mybatis的dao接口(UserDao),编写sql映射文件(UserMapper.xml)。
[创建库和表]
create database spring_mybatis default char set utf8;
create table tb_user(
id int(11) unsigned not null auto_increment,
username varchar(50) not null,
password varchar(50) not null,
money float not null default 0.0,
primary key (id)
)engine=InnoDB default charset=utf8;
[UserDao.java] 注:该接口不需要写实现类
package wjy.sm.dao;
import wjy.sm.bean.User;
/**
* 不建议使用注解,建议使用写sql映射文件
*/
public interface UserDao {
//根据用户名查找用户,建议表该列添加唯一索引
User getUserByUsername(String username);
//根据id查找用户
User findUserById(int id);
//保存一条用户信息到数据库,并获取返回的id
int savaUserGetId(User user);
}
在resource目录下新建一个mapper目录,用于存储sql的xml映射文件。
[UserMapper.xml]
<?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">
<!--namespace为DAO的包名加类名-->
<mapper namespace="wjy.sm.dao.UserDao">
<!--
查询结果映射规则配置,根据需要可以写多个resultMap。
-->
<resultMap id="userResultMap" type="wjy.sm.bean.User">
<id column="id" property="id"/>
<!--如果查询结果的列名与对象属性名相同则可以省略-->
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="money" property="money"/>
</resultMap>
<!--resultMap指定映射规则-->
<select id="getUserByUsername" parameterType="String" resultMap="userResultMap">
select * from tb_user where username = #{username}
</select>
<!--查询结果字段映射关系配置-->
<resultMap id="userResultMapAli" type="wjy.sm.bean.User">
<id column="userId" property="id"/>
</resultMap>
<select id="findUserById" parameterType="int" resultMap="userResultMapAli">
select id as userId,username,password from tb_user where id=#{id}
</select>
<!--
useGeneratedKeys 令MyBatis使用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键
要求数据库本身具备主键自动增长的功能,如mysql。
keyProperty 配置自动增长的主键的列名,如"id",如果不配置则getId为null。
返回的主键会更新到传递过来的User对象中,使用get主键可获取到值。
-->
<insert id="savaUserGetId" parameterType="wjy.sm.bean.User" useGeneratedKeys="true" keyProperty="id">
insert into tb_user (
username,
password,
money)
values (
#{username},
#{password},
#{money})
</insert>
</mapper>
在resource目录下新建一个db.properties文件。
[db.properties]
dataSource.driver = com.mysql.jdbc.Driver
dataSource.url = jdbc:mysql://localhost:3306/spring_mybatis?useUnicode=true&characterEncoding=utf8
dataSource.username = root
dataSource.password =
c3p0.initialPoolSize=5
c3p0.maxPoolSize=10
c3p0.minPoolSize=5
c3p0.maxStatements=10
c3p0.acquireIncrement=3
c3p0.maxIdleTime=60
c3p0.idleConnectionTestPeriod=120
创建applicationContext.xml文件,这是spring的配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<!-- 需要引入tx、aop、context这三个spring的名称空间 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</bean>
导入前面创建的db.properties文件中的键值对
<!-- 导入properties文件中的键值对,需要 context 名称空间-->
<context:property-placeholder location="classpath:db.properties"/>
在spring的配置文件中配置数据源,这里使用c3p0.jar包提供的ComboPooledDataSource类,即使用c3p0连接池配置数据源。
<!-- 使用c3p0连接池配置数据源,需要导入c3p0的jar包 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<!-- 是否开启自动提交,默认为false -->
<property name="autoCommitOnClose" value="false"/>
<property name="driverClass" value="${dataSource.driver}"/>
<property name="jdbcUrl" value="${dataSource.url}"/>
<property name="user" value="${dataSource.username}"/>
<property name="password" value="${dataSource.password}"/>
<!-- 池的配置 -->
<!--初始化时获取多少个连接,取值应在minPoolSize与maxPoolSize之间。默认值: 3 -->
<property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
<property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
<property name="minPoolSize" value="${c3p0.minPoolSize}"/>
<property name="maxStatements" value="${c3p0.maxStatements}"/>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。默认值: 3 -->
<property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
<!--最大空闲时间,指定多少秒内未使用则连接被丢弃。若为0则永不丢弃。默认值: 0 -->
<property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
<!--指定多少秒检查一次所有连接池中的空闲连接。默认值: 0 -->
<property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
</bean>
</beans>
这里有必要介绍一个知识点,即FactoryBean,让你看后文能知其所以然。
[FactoryBean源码]
package org.springframework.beans.factory;
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
配置SqlSessionFactory,使用mybatis-spring.jar包提供的SqlSessionFactoryBean类,该类实现了spring的FactoryBean接口。这个配置可以简单理解为:配置了一个类型为SqlSessionFactoryBean的bean,id为sqlSessionFactory,因为SqlSessionFactoryBean是实现FactoryBean接口的,所以取id为sqlSessionFactory的bean的时候[比如getBean("sqlSessionFactory")],取得的不是这个SqlSessionFactoryBean实例,而是其getObject()方法返回的实例。
<!-- mybatis-spring提供的SqlSessionFactoryBean类,该类实现了FactoryBean接口 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- mybatis的核心配置文件路径,该文件中只保留mybatis的一些设置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 接口映射文件路径,*匹配任意字符 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
public boolean isSingleton() {
return true;
}
配置实现指定mapper接口的bean(动态代理实现,这里不关注具体的实现原理,我们只要能拿到一个实现该mapper接口的实例就行了)。这个配置可以简单理解为:配置了一个类型为MapperFactoryBean的bean,id为userDao,因为MapperFactoryBean是实现FactoryBean接口的,所以取id为userDao的bean的时候[比如getBean("userDao")],取得的不是这个MapperFactoryBean实例,而是其getObject()方法返回的实例。
<!-- 配置userDao,mybatis底层使用动态代理创建注入的接口类型的实现类 -->
<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 注入Mapper接口,也可以构造方法注入 -->
<!--<property name="mapperInterface" value="wjy.sm.dao.UserDao"/>-->
<constructor-arg index="0" value="wjy.sm.dao.UserDao"/>
<!-- 注入SqlSessionFactory -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
public Class<T> getObjectType() {
return this.mapperInterface;
}
public boolean isSingleton() {
return true;
}
MapperFactoryBean实现FactoryBean接口的三个接口源码。
上面方法不推荐使用了,因为 mapper接口多的时候配置太麻烦,也使配置文件臃肿,下面介绍使用自动扫描mapper接口的方式,这样不需要一个个的为mapper接口配置bean。当我们需要使用这些bean时,因为每个mapper接口的实现类只有一个,bean也是单例的,所以在使用@Autowired这样声明注入时不需要指定bean的名称,spring会找到该接口类型的实现类的bean来注入,而如果想指定bean的名称可以使用bean接口的类名(首字母小写)。
<!-- 配置mapper接口的扫描(扫描mapper接口代理,注入到bean容器中) -->
<!-- id的名称为接口的类名,首字母小写 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="wjy.sm.dao"/>
</bean>
定义业务层接口,编写业务层接口的实现类。
[UserService接口]
package wjy.sm.service;
import wjy.sm.bean.User;
public interface UserService {
boolean userRegist(User user);
}
[UserService实现类]
package wjy.sm.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import wjy.sm.bean.User;
import wjy.sm.dao.UserDao;
import wjy.sm.service.UserService;
import javax.annotation.Resource;
@Service//不指定bean的名称,默认为类名且首字母小写,UserServiceImpl==>userServiceImpl
public class UserServiceImpl implements UserService {
//@Autowired //因为该接口只会有一个实现类,所以可以使用@Autowired
@Resource(name = "userDao")
private UserDao userDao;
//实现接口的userRegist方法
public boolean userRegist(User user) {
int roes = userDao.savaUserGetId(user);
if(roes==1&&user.getId()>0)
return true;
return false;
}
}
修改applicationContext.xml配置文件,开启ioc注解功能。
<context:component-scan base-package="wjy.sm.service.**"/>
编写测试类,验证整合完成
package wjy.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import wjy.sm.bean.User;
import wjy.sm.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Demo1 {
@Autowired
private UserService userService;
@Test
public void test(){
User user = new User();
user.setUsername("wujiuye");
user.setPassword("123456");
user.setMoney(1000.0f);
boolean boolen = userService.userRegist(user);
if(boolen){
System.out.println("恭喜注册成功![id="+user.getId()+"]");
}
}
}
回忆MyBatis的单独使用
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Override
public User getUserWithUsername(String name) {
SqlSession sqlSession = DBUtils.getInstance().getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.getUserByUsername(name);
sqlSession.close();
return user;
}
}
是不是见不到SqlSession的影子了,前面我写的那篇Mybatis入门介绍mybatis单独使用,记得是如何访问数据库的吗?
建议编写log4j的日记配置文件,可以查看sql语句及一些日记信息的输出。在resource目录下新建log4j.properties配置文件,这里我只是配置了控制台的打印输出,详细配置就不重复介绍了,之前写的文章都讲过。
[log4j.properties]
### 设置###
log4j.rootLogger = debug,stdout
### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
使用spring的aop功能
将上一篇将aop的文章中的例子拿来用,就是一个日记打印的例子。编写一个切面类LogAspect,使用注解@Component将其声明为一个bean,再使用注解@Aspect将其声明为一个切面。
[LogAspect.java]
package wjy.sm.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 日记切面类
*/
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* wjy.sm.service.*..*.*(..))")
private void logPointcut() {
}
/**
* 前置通知
*/
@Before("logPointcut()")
public void beforeLog(JoinPoint joinPoint){
System.out.println("========目标方法("+joinPoint.getSignature().getName()+")执行之前记录日记=======");
}
/**
* 后置通知
*/
@After("logPointcut()")
public void afterLog(JoinPoint joinPoint){
System.out.println("========目标方法执行("+joinPoint.getSignature().getName()+")之后记录日记=======");
}
/**
* 环绕通知
* @param proceedingJoinPoint
*/
//@Around("logPointcut()")
public void aroundLog(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("=== 环绕通知,目标方法执行之前 ===");
try {
proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("=== 环绕通知,目标方法执行之后 ===");
}
/**
* 后置返回通知
*/
@AfterReturning(value = "logPointcut()",returning = "result")
public void returningLog(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("目标方法名==>" + methodName +
" 返回值==> " + result);
}
/**
* 后置异常通知
*/
@AfterThrowing(value = "logPointcut()",throwing = "ex")
public void throwingLog(JoinPoint joinPoint, Exception ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("目标方法名==>" + methodName +
" 异常==>" + ex);
}
}
修改applicationContext.xml配置,在bean自动扫描配置添加该切面类所在包。
<!-- 开启ioc注解扫描 -->
<!-- wjy.sm.service.**s扫描业务接口实现类 -->
<!-- wjy.sm.aspect扫描切面类(普通的bean) -->
<context:component-scan base-package="wjy.sm.service.**,wjy.sm.aspect"/>
然后开启aop注解功能
<!--开启aop注解扫描-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
运行测试代码,查看通知是否切入成功。
配置spring对数据库的事务管理
修改applicationContext.xml配置文件,开启事务注解功能并配置数据库的事务管理者。使用spring-jdbc.jar包提供的DataSourceTransactionManager类配置数据库事务管理者。
<!-- 开启spring事务,依赖spring-jdbc的jar包 -->
<!-- 数据库事务管理器,建议id设为transactionManager -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解功能 -->
<tx:annotation-driven></tx:annotation-driven>
这里只介绍声明式事务管理,使用@Transactional注解。
[@Transactional]
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
将UserService的实现类修改为如下。
package wjy.sm.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import wjy.sm.bean.User;
import wjy.sm.dao.UserDao;
import wjy.sm.service.UserService;
import javax.annotation.Resource;
@Service//不指定bean的名称,默认为类名且首字母小写,UserServiceImpl==>userServiceImpl
//@Transactional
public class UserServiceImpl implements UserService {
//@Autowired //因为该接口只会有一个实现类,所以可以使用@Autowired
@Resource(name = "userDao")
private UserDao userDao;
//可以在类上添加,也可以在方法上添加,添加在方法上只对该方法有效
//不指定事务管理器的id,默认是"transactionManager",所以需要修改配置文件将事务管理器的id改为"transactionManager"
//回滚的配置必须要求异常是RuntimeException或其子类才会起作用
//rollbackFor:当异常类型为指定类型时才会回滚事务
//noRollbackFor:当异常类型为指定类型时不回滚事务
@Transactional(transactionManager = "dataSourceTransactionManager",
noRollbackFor = {ArithmeticException.class},
isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRED)
public boolean userRegist(User user) {
int roes = userDao.savaUserGetId(user);
roes/=0;//制造异常
if(roes==1&&user.getId()>0)
return true;
return false;
}
}
在userRegist方法中要做的事情就是将user信息保存到数据库,而我在该方法中模拟抛出了一个异常,当抛出异常时如果不指定noRollbackFor就会执行事务回滚。
将roes/=0;这句注释掉,运行测试方法可以看到控制台输出如下信息:(测试方法还是上面测试整合是否成功的那个代码)
========目标方法(userRegist)执行之前记录日记=======
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7fb95505]
JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@3abada5a [wrapping: com.mysql.jdbc.JDBC4Connection@50f62e81]] will be managed by Spring
==> Preparing: insert into tb_user ( username, password, money) values ( ?, ?, ?)
==> Parameters: wujiuye(String), 123456(String), 1000.0(Float)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7fb95505]
========目标方法执行(userRegist)之后记录日记=======
目标方法名==>userRegist 返回值==> true
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7fb95505]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7fb95505]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7fb95505]
恭喜注册成功![id=26]
isolation事务隔离级别
@Transactional(transactionManager = "dataSourceTransactionManager",
isolation = Isolation.READ_COMMITTED)
isolation配置事务的隔离级别,Isolation枚举类型:
package org.springframework.transaction.annotation;
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
DEFAULT:默认值,使用底层数据库的默认隔离级别。对大部分数据库而言就是READ_COMMITTED。
READ_UNCOMMITTED:一个事务可以读取另一个事务修改但还没有提交的数据。(不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别)
READ_COMMITTED:一个事务只能读取另一个事务已经提交的数据。(可以防止脏读,这也是大多数情况下的推荐值)
REPEATABLE_READ:一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。(可以防止脏读和不可重复读)
SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。(可以防止脏读、不可重复读以及幻读)(严重影响程序的性能)
propagation事务传播
@Transactional(transactionManager = "dataSourceTransactionManager",
propagation = Propagation.REQUIRED)
如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。propagation配置事务传播行为,Propagation枚举类型:
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。
readOnly只读事务
readOnly配置事务只读属性,只读事务用于客户代码只读但不修改数据的情形。
@Transactional(transactionManager = "dataSourceTransactionManager",
readOnly = true)
timeout事务超时
timeout配置事务超时,单位是秒,指定一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
@Transactional(transactionManager = "dataSourceTransactionManager",
timeout = 6)
配置回滚规则
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
@Transactional(transactionManager = "dataSourceTransactionManager",
rollbackFor = {ArithmeticException.class})
@Transactional(transactionManager = "dataSourceTransactionManager",
rollbackForClassName = {"java.lang.ArithmeticException"})
@Transactional(transactionManager = "dataSourceTransactionManager",
noRollbackFor = {ArithmeticException.class})
@Transactional(transactionManager = "dataSourceTransactionManager",
noRollbackForClassName = {"java.lang.ArithmeticException"})
附完整的applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 需要引入tx、aop、context这三个spring的名称空间 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 导入properties文件中的键值对,需要 context 名称空间-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 使用c3p0连接池配置数据源,需要导入c3p0的jar包 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<!-- 是否开启自动提交,默认为false -->
<property name="autoCommitOnClose" value="false"/>
<property name="driverClass" value="${dataSource.driver}"/>
<property name="jdbcUrl" value="${dataSource.url}"/>
<property name="user" value="${dataSource.username}"/>
<property name="password" value="${dataSource.password}"/>
<!-- 池的配置 -->
<!--初始化时获取多少个连接,取值应在minPoolSize与maxPoolSize之间。默认值: 3 -->
<property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
<property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
<property name="minPoolSize" value="${c3p0.minPoolSize}"/>
<property name="maxStatements" value="${c3p0.maxStatements}"/>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。默认值: 3 -->
<property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
<!--最大空闲时间,指定多少秒内未使用则连接被丢弃。若为0则永不丢弃。默认值: 0 -->
<property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
<!--指定多少秒检查一次所有连接池中的空闲连接。默认值: 0 -->
<property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
</bean>
<!-- mybatis-spring提供的SqlSessionFactoryBean类,该类实现了FactoryBean接口 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- mybatis的核心配置文件路径,该文件中只保留mybatis的一些设置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 接口映射文件路径,*匹配任意字符 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- 配置mapper接口的扫描 -->
<!-- id为接口的类名,首字母小写 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="wjy.sm.dao"/>
</bean>
<!-- 开启ioc注解扫描 -->
<!-- wjy.sm.service.**s扫描业务接口实现类 -->
<!-- wjy.sm.aspect扫描切面类(普通的bean) -->
<context:component-scan base-package="wjy.sm.service.**,wjy.sm.aspect"/>
<!--开启aop注解扫描-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 开启spring事务,依赖spring-jdbc的jar包 -->
<!-- 数据库事务管理器,建议id设为transactionManager -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解功能 -->
<tx:annotation-driven></tx:annotation-driven>
</beans>
以上是关于Spring与Mybatis整合事务管理的主要内容,如果未能解决你的问题,请参考以下文章