Spring与Mybatis整合事务管理

Posted 全栈攻城狮之道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring与Mybatis整合事务管理相关的知识,希望对你有一定的参考价值。

往期精彩回顾
Spring入门(二) AOP
Spring入门(二) 理解动态代理
Spring入门(一) IOC


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(11unsigned not null auto_increment,
    username varchar(50not null,
    password varchar(50not 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,让你看后文能知其所以然。

Spring中有两种类型的Bean,一种是普通Bean,其返回的对象是指定类的一个实例,另一种是实现FactoryBean接口的Bean,实现FactoryBean接口的Bean跟普通Bean不同,其返回的对象是该实现FactoryBean接口的实例的getObject方法所返回的对象。
FactoryBean通常是用来创建比较复杂的Bean,一般的Bean直接用xml配置即可,但如果一个Bean的创建过程中涉及到很多其他的bean和复杂的逻辑,这时需要用FactoryBean。

[FactoryBean源码]


package org.springframework.beans.factory;

public interface FactoryBean<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;
    }


SqlSessionFactoryBean实现FactoryBean接口的三个接口源码。


配置实现指定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]


1

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:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。(可以防止脏读、不可重复读以及幻读)(严重影响程序的性能)


2

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。


3

readOnly只读事务


readOnly配置事务只读属性,只读事务用于客户代码只读但不修改数据的情形。

@Transactional(transactionManager = "dataSourceTransactionManager",
        readOnly = true)


4

timeout事务超时

timeout配置事务超时,单位是秒,指定一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。

@Transactional(transactionManager = "dataSourceTransactionManager",
        timeout = 6)


5

配置回滚规则


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整合事务管理





以上是关于Spring与Mybatis整合事务管理的主要内容,如果未能解决你的问题,请参考以下文章

mybatis与spring整合

Mybatis与Spring整合

MyBatis与Spring的整合(Eclipse版本和IDEA版本)

MyBatis与Spring整合

MyBatis与Spring的整合

Spring与MyBatis整合