SSM Chapter 07 MyBatis 与 Spring 的整合 笔记

本章目标:

  • 掌握Spring与MyBatis的集成
  • 掌握使用SqlSessionTemplate实现整合
  • 掌握使用MapperFactoryBean实现整合
  • 掌握Spring的事务切面实现声明式事务处理
  • 掌握使用注解实现声明式事务处理

技术内容:

我们已经学习了在MyBatis的基础知识,能够使用MyBatis 进行数据库操作.而Spring框架通过IoC.AOP等机制,能够对项目中的组件进行解耦合管理,建立一个低耦合的应用架构.这大大增强了系统开发和维护的灵活性,便于功能扩展.

这里我们将利用 Spring 对 MyBatis 进行整合,在对组件实现解耦的同时,还能使MyBatis框架的使用变得更加方便和简单. 还能使MyBatis框架的使用变得更加方便和简单.

此外,通过Spring提供的声明式事务等服务,能够进一步简化代码,减少开发工作量,提高开发效率

1 . Spring 对 MyBatis 的整合思路

作为Beans容器,Spring框架提供IoC机制,可以接管所有组件的创建工作并进行依赖管理. 因而整合的主要工作是把MyBatis框架中所涉及的核心组件配置到Spring容器中,交给Spring来创建和管理.

具体来说,业务逻辑对象依赖基于MyBatis技术实现的DAO对象,核心是获取SqlSession实例.要获得SqlSession实例,则需要依赖SqlSessionFactory实例. 而SqlSessionFactory是SqlSessionFactoryBuilder 依据MyBatis配置文件中的数据源,SQL映射文件等信息来构建的.

针对上述依赖关系,以往我们需要自行编码通过SqlSessionFactoryBuilder读取配置文件,构建SqlSessionFactory,进而获得SqlSession实例,满足业务对象对于数据访问的需要.随着Spring框架的引入,以上流程将全部移交给Spring,发挥Spring 框架 Bean容器的作用,接管组件的创建工作,管理组件的生命周期,并对组件之间的依赖关系进行解耦合管理.

2 . Spring 整合 MyBatis 的准备工作

我们以超市订单系统的功能为例 来完成 Spring 与 MyBatis 的整合

1. 在项目中加入Spring MyBatis 及整合相关的 jar 文件

在使用 Spring 整合MyBatis 之前,首先要下载与整合相关的jar文件.由于Spring 3 的开发在 MyBatis 3 官方发布前就结束了,Spring 开发团队不想发布一个基于非发布版本的MyBatis的整合支持,因此Spring 3 没有提供了对MyBatis 3 的支持.

为了使Spring 3 支持 MyBatis 3, MyBatis 团队按照Spring 集成ORM框架的统一风格开发了相关的组件,令开发者可以在Spring中集成使用MyBatis

整合时,项目中需要mybatis-spring的jar文件可以在官网上下载,官网地址:http://www.mybatis.org/spring/zh/index.html 官网文档也参考此地址

由于在整合中还有用到Spring的数据源支持 以及 事务支持,因此在项目中还需要添加 spring-jdbc 和 spring-tx两个jar文件,项目所需的jar文件如下:

  • junit:junit:4.12
  • log4j:log4j:1.2.17
  • mysql:mysql-connector-java:5.1.38
  • org.springframework:spring-context:5.1.5.RELEASE
  • org.springframework:spring-aop:5.1.5.RELEASE
  • org.springframework:spring-beans:5.1.5.RELEASE
  • org.springframework:spring-core:5.1.5.RELEASE
  • org.springframework:spring-expression:5.1.5.RELEASE
  • org.aspectj:aspectjweaver:1.9.2
  • commons-logging:commons-logging:1.2
  • org.springframework:spring-test:5.1.5.RELEASE
  • org.springframework:spring-jdbc:5.1.5.RELEASE
  • org.springframework:spring-tx:5.1.5.RELEASE
  • commons-dbcp:commons-dbcp:1.4
  • commons-pool:commons-pool:1.6
  • org.mybatis:mybatis:3.5.1
  • org.mybatis:mybatis-spring:2.0.3

使用maven创建web项目,在pom.xml文件中加入如下的依赖:

  1. <properties>
  2. <spring.version>5.1.5.RELEASE</spring.version>
  3. </properties>
  4. <dependencyManagement>
  5. <!--统一声明所有关于spring依赖的jar-->
  6. <dependencies>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-framework-bom</artifactId>
  10. <version>${spring.version}</version>
  11. <type>pom</type>
  12. <scope>import</scope>
  13. </dependency>
  14. </dependencies>
  15. </dependencyManagement>
  16. <dependencies>
  17. <!--mybatis-->
  18. <dependency>
  19. <groupId>org.mybatis</groupId>
  20. <artifactId>mybatis</artifactId>
  21. <version>3.5.2</version>
  22. </dependency>
  23. <!--mybatis-spring-->
  24. <dependency>
  25. <groupId>org.mybatis</groupId>
  26. <artifactId>mybatis-spring</artifactId>
  27. <version>2.0.3</version>
  28. </dependency>
  29. <!--spring-->
  30. <dependency>
  31. <groupId>org.springframework</groupId>
  32. <artifactId>spring-context</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework</groupId>
  36. <artifactId>spring-aspects</artifactId>
  37. </dependency>
  38. <!--spring-jdbc-->
  39. <dependency>
  40. <groupId>org.springframework</groupId>
  41. <artifactId>spring-jdbc</artifactId>
  42. </dependency>
  43. <!--junit-->
  44. <dependency>
  45. <groupId>junit</groupId>
  46. <artifactId>junit</artifactId>
  47. <version>4.12</version>
  48. </dependency>
  49. <!--mysql驱动-->
  50. <dependency>
  51. <groupId>mysql</groupId>
  52. <artifactId>mysql-connector-java</artifactId>
  53. <version>5.1.38</version>
  54. </dependency>
  55. <!--dbcp-->
  56. <dependency>
  57. <groupId>org.apache.commons</groupId>
  58. <artifactId>commons-dbcp2</artifactId>
  59. <version>2.6.0</version>
  60. </dependency>
  61. </dependencies>

分析 :

解决问题的步骤如下 :

  • (1) 获取Spring 开发包 并为 工程添加Spring支持
  • (2) 为业务层 和 数据访问层 设计接口,声明所需方法
  • (3) 编写数据访问层UserDao的实现类,完成具体的持久化操作
  • (4) 在业务实现类中声明 UserDao 接口类型的属性 , 并添加适当的构造方法为属性赋值
  • (5) 在Spring的配置文件中,将DAO对象以构造注入的方式,赋值给业务实例中的UserDao类型的属性
  • (6) 在代码中获取Spring配置文件中装配好的业务类对象,实现程序功能

2. 建立目录结构,创建实体类

在cn.foo.pojo包下创建实体类User.

public class User implements Serializable {
    private Integer id;            //用户id
    private String userCode;    //用户编码
    private String userName;    //用户名称
    private String userPassword;//用户密码
    private Integer gender;        //性别
    private Date  birthday;        //生日
    private String phone;        //联系方式
    private String address;        //地址
    private Integer userRole;    //用户角色id
    private Integer createdBy;    //创建者
    private Date creationDate;    //创建时间
    private Integer modifyBy;    //更新者
    private Date modifyDate;    //更新日期
    //省略getter和setter方法 以及toString方法
  }

3. 创建数据访问接口

在cn.foo.dao.user 包中创建 实体类 User 对应的 DAO接口 UserMapper.这里先添加一个查询所有用户信息的方法,其他方法在需要时添加.

public interface UserMapper {
    /**
     * 查询所有的用户列表
     * @return
     */
    List<User> getUserList();
}

4. 配置SQL映射文件

同样在cn.smbms.dao.user包中,为UserMapper配置SQL语句映射文件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">
<mapper namespace="cn.foo.dao.user.UserMapper">
  <!--查询用户列表-->
  <select id="getUserList" resultType="user">
    select * from smbms_user
  </select>
</mapper>

5. 配置MyBatis 配置文件

编写MyBatis配置文件 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"/>
    <!-- 这个配置使全局的映射器启用或禁用缓存 -->
    <setting name="cacheEnabled" value="true"/>
    <!-- :设置驱动程序等待数据库响应的秒数 -->
    <setting name="defaultStatementTimeout" value="5"/>
    <!-- 
         启用从经典数据库列名A_COLUMN到驼峰式经典Java属性名aColumn的自动映射-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!--允许JDBC 生成主键。需要驱动器支持。如果设为了true,
这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 -->
    <setting name="useGeneratedKeys" value="true"/>
  </settings>
  <!--为实体类指定别名-->
  <typeAliases>
    <package name="cn.foo.pojo"/>
  </typeAliases>
</configuration>

注意 :

在上述代码中的MyBatis 配置文件内容 与之前的相比简单了许多,这是因为Spring 可以接管MyBatis配置信息的维护工作.这里我们选择把数据源配置和SQL映射信息转移至 Spring 配置文件中进行管理,以了解如何在Spring中配置MyBatis.

3 . 实现Spring 对 MyBatis 的整合

如前文所述,Spring需要依次完成加载MyBatis配置信息,构建SqlSessionFactory 和 SqlSession实例,完成对业务逻辑对象的依赖注入等工作.需要注意的是,这些工作大多以配置文件的方式实现,无须编写相关类,大大简化了开发且更容易维护.Spring配置文件中的主要配置如下:

3.1 配置数据源

对于任何持久化解决方案,数据库连接都是首先要解决的问题. 在Spring 中,数据源作为一个重要的组件,可以进行单独配置和维护.接下来我们将MyBatis配置中有关数据源的配置,转移到Spring 配置文件中进行维护.

配置DBCP

在Spring中配置数据源,首先要选择一种具体的数据源实现技术. 目前流行的数据源有 dbcp,c3p0,druid..等,它们都实现了连接池的功能.

这里以配置dbcp数据源为例进行讲解,其他的数据源配置方法与此类似,大家可以自行查阅官方网站以及相关资料进行学习. dbcp数据源隶属于 Apache Commons 项目,因此使用dbcp数据源就需要下载 相关的jar文件(这里包括两个,分别是: commons-dbcp-1.4.jar 和 commons-pool-1.4.jar).

当然如果是maven项目,则需要在pom.xml文件中加入相关的依赖,前文中我们已经加入了. 依赖代码如下:

<!--commons - dbcp-->
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
<!--commons-pool-->
<dependency>
    <groupId>commons-pool</groupId>
    <artifactId>commons-pool</artifactId>
    <version>1.6</version>
</dependency>

也可使用dbcp2 包含了以上两个依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.6.0</version>
</dependency>

建立Spring 配置文件 applicationContext-mybatis.xml,配置数据源的关键代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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">
  <!--配置数据源  BasicDataSource类实现了DataSource接口,可以用于DBCP连接池的简单使用-->
  <bean id="dataSource"
        class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url"
              value="jdbc:mysql://127.0.0.1:3306/foo?useUnicode=true&amp;characterEncoding=utf-8"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
  </bean>
</beans>

解释 BasicDataSource 配置如下:

参数 描述
username 传递给JDBC驱动的用于建立连接的用户名
password 传递给JDBC驱动的用于建立连接的密码
url 传递给JDBC驱动的用于建立连接的URL
driverClassName 使用的JDBC驱动的完整有效的java 类名

注意:因为url属性的值包含特殊符号 “ & “,所以赋值是使用了实体引用 “**&amp;**“,当然也可以使用**<![CDATA[]]>**标记

其他配置可参考官网 : http://commons.apache.org/proper/commons-dbcp/configuration.html

配置DriverManagerDataSource

使用Spring内置的连接池DriverManagerDataSource ,配置数据源, 代码如下:

<!--注入spring内置的数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url"
            value="jdbc:mysql://127.0.0.1:3306/foo?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"/>
  <property name="username" value="root"/>
  <property name="password" value="root"/>
</bean>

参考官网如下:

02.png

3.2 配置SqlSessionFactoryBean

配置完数据源之后,就可以在此基础上集合 SQL映射文件信息 以及 MyBatis配置文件中的其他信息,创建SqlSessionFactory 实例

在MyBatis中,SqlSessionFactory 的实例需要使用SqlSessionFactoryBuilder 创建,而在集成环境中,则可以使用MyBatis-Spring 整合包中的 SqlSessionFactoryBean来代替.SqlSessionFactoryBean封装了使用SqlSessionFactoryBuilder 创建 SqlSessionFactory 的过程,我们可以在Spring中以配置文件的形式,通过SqlSessionFactoryBean 获得 SqlSessionFactory 实例.

代码如下:

<!--配置sqlSessionFactoryBean-->
<bean id="sqlSessionFactory"  class="org.mybatis.spring.SqlSessionFactoryBean">
  <!--引用数据源组件-->
  <property name="dataSource" ref="dataSource"/>
  <!--引用MyBatis配置文件中的配置-->
  <property name="configLocation" value="classpath:mybatis-config.xml"/>
  <!--配置SQL映射文件信息-->
  <!-- <property name="mapperLocations"> -->
  <!--此处注意classpath 与 classpath* 的区别:
     classpath:只会到你的class路径中查找找文件。
     classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找。
  注意: 用classpath*:需要遍历所有的classpath,所以加载速度是很慢的;
  因此,在规划的时候,应该尽可能规划好资源文件所在的路径,尽量避免使用classpath*。-->

  <!-- classpath*的使用:当项目中有多个classpath路径,并同时加载多个classpath路径下
  (此种情况多数不会遇到)的文件,*就发挥了作用,如果不加*,则表示仅仅加载第一个classpath路径-->
  <!--  <list>
         <value>classpath*:cn/foo/dao/**/*.xml</value>
        </list>
</property>    -->
  <property name="mapperLocations"
            value="classpath*:cn/foo/mapper/*.xml" />
</bean>

上述代码中 通过 配置 id 为 sqlSessionFactory 的 Bean 即可获得 SqlSessionFactory实例

注意

逐个列出所有的SQL 映射文件比较烦琐, 在SqlSessionFactoryBean的配置中可以是使用mapperLocations属性扫描式加载SQL映射文件. 其中,"classpath*:cn/smbms/dao/**/*.xml"表示扫描 cn.smbms.dao 包及其任意层级子包中,任意名称的xml 类型文件.

此处注意**** 的区别:**classpath**``**classpath***

**classpath**:只会到你的class路径中查找文件。
**classpath***:不仅包含class路径,还包括jar文件中(class路径)进行查找。

注意: 用**classpath***:需要遍历所有的**classpath**,所以加载速度是很慢的;

**因此,在规划的时候,应该尽可能规划好资源文件所在的路径,尽量避免使用classpath

的使用:当项目中有多个classpath路径,并同时加载多个classpath路径下(此种情况多数不会遇到)的文件,就发挥了作用,如果不加,则表示仅仅加载第一个classpath路径**classpath***``*``*

除了数据源和SQL映射信息,其他的MyBatis 配置信息也可以转移至Spring 配置文件中进行维护,只需要通过SqlSessionFactoryBean 的 对应属性进行赋值 即可. 具体可查阅官网 以及相关资料进行学习.

3.3 使用SqlSessionTemplate 实现数据库的操作

SqlSessionTemplate 类实现了MyBatis 的SqlSession接口,可以替换MyBatis中原有的SqlSession实现类提供数据库访问操作.

使用SqlSessionTemplate可以更好的与Spring 服务融合并简化部分流程化的工作,可以保证和当前Spring事务相关联,自动管理会话的生命周期,包括必要的关闭,提交和回滚操作.

**SqlSessionTemplate** 是 MyBatis-Spring 的核心。作为 **SqlSession** 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 **SqlSession****SqlSessionTemplate** 是线程安全的,可以被多个 DAO 或映射器所共享使用。

当调用 SQL 方法时(包括由 **getMapper()** 方法返回的映射器中的方法),**SqlSessionTemplate** 将会保证使用的 **SqlSession** 与当前 Spring 的事务相关。此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。另外,它也负责将 MyBatis 的异常翻译成 Spring 中的 **DataAccessExceptions**

由于模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该用 **SqlSessionTemplate** 来替换 MyBatis 默认的 **DefaultSqlSession** 实现。避免在同一应用程序中的不同类之间混杂使用时 , 可能会引起数据一致性的问题。

可以使用 **SqlSessionFactory** 作为构造方法的参数来创建 **SqlSessionTemplate** 对象

配置SqlSessionTemplate 并在 UserMapper实现类中使用SqlSessionTemplate类,具体代码如下:

/**
 * 定义 DAO 接口的实现类,实现UserMapper接口
 */
public class UserMapperImpl implements UserMapper {
    private SqlSession sqlSession;

    public void setSqlSession(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }
    @Override
    public List<User> getUserList() {
        return sqlSession.selectList("cn.foo.dao.user.UserMapper.getUserList");
    }

}

Spring 配置文件中的关键代码如下:

<!--定义sqlSessionTemplate实例时,需要通过构造方法注入 sqlSessionFactory实例,
    这里引用的是id 为 sqlSessionFactory的bean-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!-- 配置DAO组件 并 注入 SqlSessionTemplate 实例 -->
<bean id="userMapper" class="cn.foo.dao.user.impl.UserMapperImpl">
    <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
</bean>

注意:

  1. 创建SqlSessionTemplate 实例时,需要通过其构造方法注入SqlSessionFactory实例.这里引用的是前文配置过的id为sqlSessionFactory的Bean
  2. 与MyBatis 中默认的SqlSession 实现不同,SqlSessionTemplate是线程安全的,可以以单例模式配置并被多个DAO对象共用,而且不必为每个DAO单独配置一个SqlSessionTemplate.

3.4 编写业务逻辑代码并测试

完成DAO组件的装配,接下来就可以在cn.foo.service.user 包中开发业务组件,并通过Spring装配.代码如下:

业务接口的关键代码:

public interface UserService {
    /**
     * 查询所用的用户信息
     * @return
     */
    List<User> findUserList();
}

业务接口实现类的关键代码:

public class UserServiceImpl implements UserService {
    private UserMapper userMapper;
    @Override
    public List<User> findUserList() {
        return userMapper.getUserList();
    }

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
}

Spring 配置文件中的关键代码:

<!--配置业务Bean 并注入 DAO 实例-->
<bean id="userService"
      class="cn.foo.service.user.impl.UserServiceImpl">
    <property name="userMapper" ref="userMapper"/>
</bean>

完成所有组件的开发和装配之后,接下来就可以测试整合的效果了,测试方法中的关键代码如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-mybatis.xml")
public class TestUserService {
    private final Logger logger = Logger.getLogger(this.getClass());
    @Autowired
    private UserService userService;
    @Test
    public void testGetUserList(){
        List<User> userList = userService.findUserList();
        userList.forEach(logger :: info);
    }
}

Eclipse 运行测试代码,结果正确输出.但是,请注意 若是使用 IDEA 运行测试代码时,发现程序报错,报错信息如下:

###Caused by: org.springframework.core.NestedIOException: Failed to parse mapping resource: 'class path resource [cn/smbms/dao/user/UserMapper.xml]'; nested exception is java.io.FileNotFoundException: class path resource [cn/smbms/dao/UserMapper.xml] cannot be opened because it does not exist

这两句报错信息,很明显说的是 mapper.xml里的namespace报错 或者是方法名报错,但是仔细排查发现并未出现错误.错误原因究竟在哪儿呢?

这是因为 IDEA 在使用maven编译的时候,如果配置文件不是放在resources文件夹下就不会被执行编译,所以导致运行时找不到对应的xml映射文件。

解决方法:

  • (1) 在main/resources目录下创建 mapper目录, 将对应的xml映射文件拖到mapper目录下,然后修改applicationContext-mybatis.xml文件中的 SqlSessionFactory的属性 mapperLocations,改为如下代码:
    <!--配置SQL映射文件信息-->
    <!--<property name="mapperLocations">
     <list>
         <value>classpath*:mapper/*Mapper.xml</value>
     </list>
    </property>-->
    <property name="mapperLocations"
                 value="classpath*:mapper/*.xml" />
    

第一种方式的弊端是:破坏了项目的包结构.若项目代码已经很多了,而且很多人都在用,改变了整体结构就会变得很麻烦,所以建议采用第二种方式:

  • (2) 在项目pom.xml文件中找到<build></build>标签,在其中添加以下几行,用于扫描xml文件
    <resources>
     <resource>
         <directory>src/main/java</directory>
         <includes>
             <include>**/*.xml</include>
         </includes>
     </resource>
    </resources>
    

小结:

利用Spring框架和MyBatis-Spring整合资源提供的组件,能够以配置的方式得到数据源、SqlSessionFactoryBean、SqlSessionTemplate 等组件,并在此基础上完成DAO模块和业务模块的开发和装配,简化了开发过程且便于维护.

知识扩展:

针对SqlSessionTemplate 的使用,MyBatis-Spring 还提供了 SqlSessionDaoSupport 类 来简化 SqlSessionTemplate 的配置和获取. SqlSessionDaoSupport 用法如下:

修改 UserMapper的实现类,注视掉SqlSessionTemplate属性以及对应的setter方法,然后继承SqlSessionDaoSupport:

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> getUserList() {
        return this.getSqlSession().selectList("cn.foo.dao.user.UserMapper.getUserList");
    }
}

Spring 配置文件中的关键代码如下:

<!--将之前id 为 sqlSessionFactory的bean注视掉,重新配置dao-->
<bean id="userMapper" class="cn.foo.dao.user.impl.UserMapperImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

SqlSessionDaoSupport类提供了setSqlSessionFactory()方法用来注入 SqlSessionFactory实例并创建SqlSessionTemplate实例,同时提供了getSqlSession()方法来返回创建好的SqlSessionTemplate实例.

这样,DAO实现类 只需要 继承自SqlSessionDaoSupport类,就可以通过调用getSqlSession()方法获得创建好的SqlSessionTemplate实例,无须额外定义 SqlSession 属性 和 setter方法.

而Spring的配置文件中 也无须再配置SqlSessionTemplate,只需要通过DAO对象的setSqlSessionFactory()方法为其注入SqlSessionFactory 即可,在一定程度上简化了DAO组件的开发.

如图:03.png

4 . 注入映射器的实现

为了不需要一个个地注册所有的映射器。你可以让 MyBatis-Spring 对类路径进行扫描来发现它们。

有几种办法来发现映射器:

  • 使用 <mybatis:scan/> 元素
  • 使用 @MapperScan 注解
  • 在经典 Spring XML 配置文件中注册一个 MapperScannerConfigurer

和 都在 MyBatis-Spring 1.2.0 中被引入。 需要你使用 Spring 3.1+。**<mybatis:scan/>**``**@MapperScan**``**@MapperScan**

4.1 使用MapperFacotoryBean注入映射器

与其在数据访问对象(DAO)中手工编写使用 SqlSessionDaoSupport 或 SqlSessionTemplate 的代码,还不如让 Mybatis-Spring 为你创建一个线程安全的映射器,这样你就可以直接注入到其它的 bean 中了

注册映射器的方法根据你的配置方法,即经典的 XML 配置或新的 3.0 以上版本的 Java 配置(也就是常说的 @Configuration),而有所不同。

注意 :

SQL 映射文件中须遵循以下命名规则:

  • 映射文件中的命名空间 和 映射接口 的名称相同;
  • 映射元素的id 和 映射器 接口的方法相同;

在之前案例的基础上,删除UserMapper的实现类UserMapperImpl,仅保留 UserMapper 接口 和 相关的SQL映射文件,在Spring 配置文件中,重新配置DAO组件,关键代码如下:


<!--配置sqlSessionFactoryBean-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <!--引用数据源组件-->
  <property name="dataSource" ref="dataSource"/>
  <!--引用MyBatis配置文件中的配置-->
  <property name="configLocation" value="classpath:mybatis-config.xml"/>
  <!--配置SQL映射文件的信息-->
  <!-- <property name="mapperLocations">
            <list>
            <value>classpath*:cn/foo/dao/**/*.xml</value>
            </list>
     </property>
 <property name="mapperLocations"
                  value="classpath*:cn/foo/mapper/*.xml" />-->
</bean>
<!--配置DAO-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
  <!--也可以使用构造注入的方式 注入接口的完全限定名-->
  <!-- <constructor-arg value="cn.foo.dao.UserMapper"/>-->
  <property name="mapperInterface" value="cn.foo.dao.user.UserMapper"/>
</bean>
<!--配置业务Bean 并注入 DAO 实例-->
<bean id="userService" class="cn.foo.service.user.impl.UserServiceImpl">
  <property name="userMapper" ref="userMapper"/>
</bean>

业务组件的定义 和 配置以及测试代码与之前的相同.无须手工编码定义UserMapper的实现类. 通过配置MapperFactoryBean即可自动生成,减少了DAO模块的编码工作量.

注意:

  • (1) 配置DAO组件 userMapper时,class属性不是某个实现类,而是MapperFactoryBean.
  • (2) 通过mapperInterface 属性 指定映射器, 而且只能是接口类型,不能是某个实现类.
  • (3) MapperFactoryBean 是 SqlSessionDaoSupport 的子类,需要通过 setSqlSessionFactory()方法注入SqlSessionFactory实例 以创建 SqlSessionTemplate 实例.
  • (4) 如果映射器对应的SQL文件与映射器的类路径相同,该映射文件可以自动被MapperFactoryBean解析.在此情况下,配置SqlSessionFactoryBean 时 可以不必指定 SQL 映射文件的位置.

如上述代码 将 SqlSessionFactoryBean中元素的属性 mapperLocations 注释掉之后,程序仍能正常运行;反之,若映射器与映射文件的类路径不同,则仍需在配置SqlSessionFactoryBean时 明确指定 映射文件的位置.

4.2 使用MapperScannerConfigurer注入映射器:

在Spring配置文件中 使用 MapperFactoryBean 对映射器做配置,简化了DAO模块的编码,不过如果映射器很多,相应的配置项应该也会很多.

为了简化配置工作量,MyBatis-Spring 中提供了MapperScannerConfigurer,它可以扫描指定包中的接口,并将它们直接注册为MapperFactoryBean.

MapperScannerConfigurer 的配置方法如下:

<!--配置sqlSessionFactoryBean-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <!--引用数据源组件-->
  <property name="dataSource" ref="dataSource"/>
  <!--引用MyBatis配置文件中的配置-->
  <property name="configLocation" value="classpath:mybatis-config.xml"/>
  <!--配置SQL映射文件的信息-->
  <!-- <property name="mapperLocations">
            <list>
            <value>classpath*:cn/foo/dao/**/*.xml</value>
            </list>
     </property>-->
</bean>
<!--配置DAO 注意:这里的sqlSessionFatory是按照类型自动注入的 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <!--注意:这里的sqlSessionFatory是按照类型自动注入的  -->
  <!--basePackage 属性 指定了扫描的基准包 MapperScannerConfigurer将递归扫描基准包下所有的接口
        并将它们动态注册为MapperFactoryBean,如此即可批量生产映射器实现类-->
  <property name="basePackage" value="cn.foo.dao"/>
</bean>

basePackage 属性 指定了扫描的基准包 MapperScannerConfigurer将递归扫描基准包(包括各层级子包)下所有的接口. 如果他们在SQL映射文件中定义过,则将它们动态注册为MapperFactoryBean,如此即可批量生产映射器实现类

注意 :

  • (1) basePackage 属性中可以包含多个包名,多个包名之间可以使用逗号或分号隔开
  • (2) MapperScannerConfigurer 会为所有由它创建的映射器开启自动装配。
    也就是说:MapperScannerConfigurer创建的所有映射器实现都会(按照类型)被自动注入SqlSessionFactory 实例。**因此在上面代码中 配置DAO组件时,无须显式注入SqlSessionFactory实例。**
  • (3) 若环境中处于不同目的配置了多个SqlSessionFactory实例,自动装配将无法进行,此时应显式指定所依赖的SqlSessionFactory实例。

配置方式如下:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <!--basePackage 属性 指定了扫描的基准包 MapperScannerConfigurer将递归扫描基准包下所有的接口
        并将它们动态注册为MapperFactoryBean,如此即可批量生产映射器实现类-->
  <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
  <property name="basePackage" value="cn.foo.dao"/>
</bean>

注意

  • 此处使用的是sqlSessionFactoryBeanName属性而不是sqlSessionFactory属性,正如该属性名所表达的,这个属性关注的是Bean的名称,所以为其赋值使用的是value属性 而不是 ref属性.

通过配置MapperScannerConfigurer将映射器注册到Spring容器时,Spring会根据其接口名称为其命名,默认规则是首字母小写的非完全限定类名。

例如:UserMapper类型的组件会被默认命名为userMapper。按此命名规则,依然可以在Spring配置文件中按如下方式按业务组件注入映射器。

此处使用的是sqlSessionFactoryBeanName属性而不是sqlSessionFactory属性,正如该属性名所表达的,这个属性关注的是Bean的名称,所以为其赋值使用的是value属性 而不是 ref属性.

通过配置MapperScannerConfigurer将映射器注册到Spring容器时,Spring会根据其接口名称为其命名,默认规则是首字母小写的非完全限定类名.例如:UserMapper类型的组件会被默认命名为userMapper.按此命名规则,依然可以在Spring配置文件中按如下方式按业务组件注入映射器.

代码如下:

<!--配置业务Bean 并注入 DAO 实例-->
<bean id="userService" class="cn.foo.service.user.impl.UserServiceImpl">
  <property name="userMapper" ref="userMapper"/>
</bean>

当然,更普遍的做法是,在MapperScannerConfigurer 自动完成映射器注册之后,使用@Autowired 或者 @Resource 注解实现对业务组件的依赖注入,以简化业务组件的配置. 使用注解对业务组件的定义和配置如下:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public List<User> findUserList() {
        return userMapper.getUserList();
    }

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
}

Spring 配置文件中的关键代码如下:

<?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:context="http://www.springframework.org/schema/context"
       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">
  <!--开启注解扫描-->
  <context:component-scan base-package="cn.foo.service"/>
  <!--省略配置数据源-->
  <!--省略 配置sqlSessionFactoryBean-->
  <!--省略 配置DAO -->
</beans>

注意:Spring 配置文件中需要引入 context 命名空间

测试代码与之前相同

4.3 <mybatis:scan/>

<mybatis:scan/> 元素会发现映射器,它发现映射器的方法与 Spring 内建的 <context:component-scan/> 发现 bean 的方法非常类似。

示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
       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://mybatis.org/schema/mybatis-spring
                           http://mybatis.org/schema/mybatis-spring.xsd">

  <!-- 省略其他配置
  从基包开始递归搜索接口,并将其注册为MapperFactoryBeans。
        请注意,将只注册至少具有一个方法的接口;忽略具体类-->
  <mybatis:scan base-package="cn.foo.mapper"/>

</beans>

base-package 属性允许你设置映射器接口文件的基础包。通过使用逗号或分号分隔,你可以设置多个包。并且会在你所指定的包中递归搜索映射器。

注意:

  • 不需要为 <mybatis:scan/> 指定 SqlSessionFactorySqlSessionTemplate,这是因为它将使用能够被自动注入的 MapperFactoryBean。但如果你正在使用多个数据源(DataSource),自动注入可能不适合你。在这种情况下,你可以使用 factory-reftemplate-ref 属性指定你想使用的 bean 名称。
  • <mybatis:scan/> 支持基于标记接口或注解的过滤操作。在 annotation 属性中,可以指定映射器应该具有的特定注解。而在 marker-interface 属性中,可以指定映射器应该继承的父接口。当这两个属性都被设置的时候,被发现的映射器会满足这两个条件。默认情况下,这两个属性为空,因此在基础包中的所有接口都会被作为映射器被发现。

提示 <context:component-scan/> 无法发现并注册映射器。映射器的本质是接口,为了将它们注册到 Spring 中,发现映射器必须知道如何为找到的每个接口创建一个 MapperFactoryBean

4.4 知识扩展:

使用注解注入映射器:

1. 创建普通的Java类,类名为AppConfig,包名为cn.smbms.config,代码如下:

@Configuration
@ComponentScan("cn.foo.service")
@MapperScan("cn.foo.dao")
public class AppConfig {
    @Bean
    public DataSource dataSource(){
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/foo?useUnicode=true&characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean =
                new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        //为实体类设置别名
        sqlSessionFactoryBean.setTypeAliasesPackage("cn.smbms.pojo");
        return sqlSessionFactoryBean.getObject();
    }
//    @Bean
//    public MapperScannerConfigurer mapperScannerConfigurer(){
//        MapperScannerConfigurer mapperScannerConfigurer =
//                new MapperScannerConfigurer();
//        mapperScannerConfigurer.setBasePackage("cn.smbms.dao");
//        return mapperScannerConfigurer;
//    }
}

@MapperScan解释:这个注解具有与之前见过的 <mybatis:scan/> 元素一样的工作方式。它也可以通过 markerInterfaceannotationClass 属性设置标记接口或注解类

2. 创建测试类代码如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= AppConfig.class)
public class TestAppConfigUser {
    private final Logger logger = Logger.getLogger(this.getClass());
    @Autowired
    private UserService userService;
    @Test
    public void testGetUserList(){
        List<User> userList = userService.findUserList();
        userList.forEach(logger :: info);
    }
}

其他的不做改动,运行测试代码,控制台正常输出.

小结:

MapperScannerConfigurer 与 @Autowired 注解 或 @Resource 注解配置使用,自动创建映射器并注入给业务组件,最大限度的减少DAO组件与业务组件的编码和配置工作.而在实际开发中也是这种方式使用的更多,而基于全注解的配置用的很少,因此了解即可.

5 . 为业务层添加声明式事务

业务层的的职能不仅仅是调用DAO这么简单,事务处理是任何企业级应用开发中不能回避的一个重要问题. 以往我们通过在业务方法中硬编码的方式进行事务控制,这样做的弊端显而易见:事务代码分散在业务方法中难以重用,需要调整时工作量也比较大;复杂事务的编码难度较高,增加了开发难度等.

Spring 提供了声明式事务处理机制,它基于AOP实现,无须编写任何事务管理代码,所有的工作全在配置文件中完成.这意味着与业务代码完全分离,配置即可用,降低了开发和维护的难度.

5.1 配置声明式事务:

这里以添加用户的功能为例,介绍如何实现声明式事务处理.

  • 首先在UserMapper 和 UserMapper.xml中添加相关的方法和SQL映射,代码如下:
  • UserMapper.java 中 添加的关键代码:
    int add(User user);
    
  • UserMapper.xml 中 添加的关键代码:
    <!--新增用户信息-->
    <insert id="add" parameterType="user">
    insert into foo_user
    <trim prefix="(" suffix=")" suffixOverrides=",">
     <if test="userCode !=null and userCode != ''">
       userCode,
     </if>
     <if test="userName !=null and userName != ''">
       userName,
     </if>
     <if test="userPassword !=null and userPassword != ''">
       userPassword,
     </if>
     <if test="gender !=null">
       gender,
     </if>
     <if test="birthday !=null">
       birthday,
     </if>
     <if test="phone !=null and phone!='' ">
       phone,
     </if>
     <if test="address !=null and address!=''">
       address,
     </if>
     <if test="userRole !=null">
       userRole,
     </if>
     <if test="createdBy !=null">
       createdBy,
     </if>
     <if test="creationDate !=null">
       creationDate
     </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
     <if test="userCode !=null and userCode != ''">
       #{userCode},
     </if>
     <if test="userName !=null and userName != ''">
       #{userName},
     </if>
     <if test="userPassword !=null and userPassword != ''">
       #{userPassword},
     </if>
     <if test="gender !=null">
       #{gender},
     </if>
     <if test="birthday !=null">
       #{birthday},
     </if>
     <if test="phone !=null and phone!='' ">
       #{phone},
     </if>
     <if test="address !=null and address!=''">
       #{address},
     </if>
     <if test="userRole !=null">
       #{userRole},
     </if>
     <if test="createdBy !=null">
       #{createdBy},
     </if>
     <if test="creationDate !=null">
       #{creationDate}
     </if>
    </trim>
    </insert>
    

由于使用了MapperScannerConfigurer 动态创建映射器实现,没有自定义的接口实现类,也就没有相关代码需要修改.

  • 在业务组件中添加的相应的业务方法:
  • 业务接口中添加相关的业务方法,代码如下:
    boolean addNewUser(User user);
    
  • 业务实现类中的方法实现代码:
@Override
public boolean addNewUser(User user) {
    boolean result = userMapper.add(user)>0;
    //int i = 3/0;//模拟异常发生时 事务是否回滚
    return result;
}

注意:

业务类中没有事务控制的相关代码,所以将 int i = 3/0 去掉其注释时,运行测试类发现事务并没有回滚.

接下来为业务方法配置事务切面,这里需要用到 tx 和 aop 两个命名空间下的标签,所以首先在Spring配置文件中导入这两个命名空间.代码如下:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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">
  <!-- 省略具体配置 -->
</beans>

接下来需要配置一个事务管理组件,它提供了对事务处理的全面支持和统一管理,在切面中相当于增强处理的角色.这里使用Spring 提供的事务管理器类 DataSourceTransactionManager , 其配置方式如下:

<!--省略数据源 SqlSessionFactoryBean DAO 以及 业务Bean的配置-->
<!--定义事务管理器-->
<bean id="txManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <!-- <property name="dataSource" ref="dataSource"/>-->
  <constructor-arg ref="dataSource"/>
</bean>

注意:

配置**DataSourceTransactionManage** 时, 要为其注入事先定义好的数据源组件,可参考Spring的官网:**https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-first-example**,如图所示:

image-20200620173401321.png
与之前接触过的增强处理不同,事务管理器还可以进一步配置,以便更好的适应不同的业务方法对于事务的不同要求.

可以通过<tx:advice>标签配置事务增强,设定事务属性,为不同的业务方法指定具体的事务规则.其代码片段如下:

<!--通过<tx:advice>标签为指定的事务管理器 设置事务属性
  默认使用的事务管理器的bean的名字是:transactionManager,
  若定义的事务管理器的Bean的id是:transactionManager,则可以不用指定
 transaction-manager属性-->
<tx:advice id="txAdvice" transaction-manager="txManager">
  <!--定义属性,声明事务规则-->
  <tx:attributes>
    <tx:method name="find*" propagation="SUPPORTS"/>
    <tx:method name="add*" propagation="REQUIRED"/>
    <tx:method name="del*" propagation="REQUIRED"/>
    <tx:method name="update*" propagation="REQUIRED"/>
    <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

<tx:advice>标签内可以设置id属性 和 transaction-manager属性.其中 transaction-manager 属性 引用一个事务管理器Bean.

详细配置可参考官网https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-first-example 。 如图所示:

image-20200620173252909.png

注意:

transaction-manager 属性的默认值是 transactionManager,也就是说,如果定义的事务管理器Bean的名称是transactionManager,则可以不指定该属性值

除了这两个属性外,还可以通过**<tx:attributes>**子标签定制事务属性.事务属性通过**<tx:method>** 标签进行设置.Spring支持对不同的方法设置事务属性,所以可以为一个**<tx:attributes>**设置多个**<tx:method>** .

<tx:method>标签中中的name属性是必需的,用于指定匹配的方法.这里需要对方法名进行约定,可以使用通配符(*).其他属性均为可选,用于指定具体的事务规则,这些属性解释如下:

propagation : 事务传播机制.该属性可选的值 有 如下几种:

  • REQUIRED:默认值:表示如果存在事务,则支持当前事务,如果当前没有事务,则开启一个新的事务
  • PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起
  • MANDATORY(强制的):支持当前事务,如果当前没有事务,就抛出异常
  • NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • NEVER:以非事务方式执行,如果当前存在事务,则抛出异常

经验:

  • REQUIRED : 能过满足大多数的事务需求,可以作为首选的事务传播行为

isolation : 事务隔离级别.即当前事务和其他事务的隔离程度,在并发事务处理的情况下,需要考虑它的设置.该属性可选的值有如下几种:

  • DEFAULT(一般情况下使用这种配置即可):这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别
  • READ_COMMITTED:读已提交的数据(会出现不可重复读,幻读)大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统
  • READ_UNCOMMITTED:保证了读取过程中不会读取到非法数据。
  • REPEATABLE_READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失
  • SERIALIZABLE:最严格的级别,事务串行执行,资源消耗最大;

timeout:事务超时时间,允许事务运行的最长时间,以秒为单位。默认值为-1,表示不超时

read-only:事务是否为只读,默认值为false

rollback-for:设定能够触发回滚的异常类型,Spring默认只在抛出RuntimeException时才标识事务回滚,可以通过全限定类名指定需要回滚事务的异常,多个类名用逗号隔开

no-rollback-for:设定不触发回滚的异常类型,Spring默认 checked Exception 不会触发事务回滚,可以通过全限定类名指定不需回滚事务的异常,多个类名用英文逗号隔开

关于事务规则更加详细的说明,请参考官网https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-txadvice-settings

设置完事务规则,最后还要定义切面,将事务规则应用到指定方法上.代码如下:

<!--定义切面-->
<aop:config>
  <!--定义切入点-->
  <aop:pointcut id="pointcut" expression="execution(* cn.foo.service.user.*.*(..))"/>
  <!--将事务增强 与 切入点结合-->
  <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>

注意:

aop:advisor 和 advice-ref 属性引用的是通过**<tx:advice>** 标签设定了事务属性的组件.

至此 Spring的声明式事务就配置完成了,最后再总结一下配置的步骤:

  • (1) 导入 tx 和 aop 命名空间
  • (2) 定义事务管理器Bean,并为其注入数据源Bean
  • (3) 通过 **<tx:advice>**配置事务增强,绑定事务管理器并针对不同方法定义事务规则
  • (4) 配置切面,将事务增强 与 方法切入点 组合

测试方法的关键代码如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-mybatis.xml")
public class TestUserService {
    private final Logger logger = Logger.getLogger(this.getClass());
    @Autowired
    private UserService userService;
    @Test
    public void testAddNewUser() throws Exception{
        User user = new User();
        user.setUserCode("tom");
        user.setUserName("Tom");
        user.setUserPassword("123");
        user.setAddress("New York");
        user.setGender(1);
        user.setBirthday(new SimpleDateFormat("yyyy-MM-dd").parse("1990-09-09"));
        user.setPhone("13800000000");
        user.setCreatedBy(1);
        user.setCreationDate(new Date());
        boolean flag = userService.addNewUser(user);
        logger.info("是否添加成功==>"+flag);
    }
}

5.2 使用注解实现声明式事务处理

Spring 还支持使用注解配置声明式事务,所使用的注解是 @Transactional

首先 仍然需要在Spring配置文件中配置事务管理类,并添加对注解配置的事务的支持,代码如下:

<!--定义事务管理器-->
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <!-- <property name="dataSource" ref="dataSource"/>-->
  <constructor-arg ref="dataSource"/>
</bean>
<!--启用事务注解 属性 transaction-manager的默认值是 transactionManager 
 若指定事务管理器的Bean的id 为 transactionManager,此属性可以不用指定 -->
<tx:annotation-driven transaction-manager="transactionManager" />

经过如上配置,程序边支持使用@Transactional 注解来配置事务了,代码如下:

@Transactional
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public List<User> findUserList() {
        return userMapper.getUserList();
    }
    @Override
    public boolean addNewUser(User user) {
        boolean result = userMapper.add(user)>0;
        int i = 3/0;//测试程序出现RuntimeException时,事务是否回滚
        return  result;
    }
}

在业务实现类上添加@Transactional 注解即为该类的所有业务方法统一添加事务处理,如果某一业务方法需要采用不同的事务规则,可以在该业务方法上添加@Transactional注解单独进行设置.

@Transactional 注解也可以设置事务属性的值,默认的@Transactional 设置如下:

  • 事务传播设置是 PROPAGATION_REQUIRED
  • 事务隔离级别是 ISOLATION_DEFAULT
  • 事务是读/写
  • 事务超时默认是依赖于事务系统的,或者事务超时没有被支持
  • 任何 RuntimeException 将触发事务回滚,但是任何checked Exception 将不触发事务回滚.

这些默认的设置当然也是可以改变的.@Transactional注解的各属性如表:

属性 类型 说明
propagation 枚举型:Propagation 可选的传播性设置。使用举例:@Transactional(propagation=Propagation.REQUIRES_NEW)
isolation 枚举型:Isolation 可选的隔离性级别。使用举例:@Transactional(isolation=Isolation.READ_COMMITTED)
readOnly 布尔型 是否为只读型事务。使用举例:@Transactional(readOnly=true)
timeout int型(以秒为单位) 事务超时。使用举例:Transactional(timeout=10)
rollbackFor 一组Class类的实例,必须是Throwable的子类 一组异常类,遇到时必须 回滚。使用举例:@Transactional(rollbackFor={SQLException.class}),多个异常用逗号隔开
rollbackForClassName 一组Class类的名字,必须是Throwable的子类 一组异常类名,遇到时必须回滚。使用举例:@Transactional( rollbackForClassName={“SQLException”}),多个异常用逗号隔开
noRollbackFor 一组Class类的实例,必须是Throwable的子类 一组异常类,遇到时必须不回滚
noRollbackForClassName 一组Class类的名字,必须是Throwable的子类 一组异常类名,遇到时必须不回滚

5.3 知识扩展:

全部使用注解实现编程式事务管理:

修改配置类的代码:

@Configuration
@ComponentScan(basePackages = "cn.smbms.service")
@MapperScan(basePackages = "cn.smbms.mapper")
@EnableTransactionManagement//启用事务管理注解
public class AppConfig {
    @Bean
    public DataSource dataSource(){
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/smbms?useUnicode=true&characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean =
                new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        sqlSessionFactoryBean.setTypeAliasesPackage("cn.smbms.pojo");
        return sqlSessionFactoryBean.getObject();
    }
    @Bean
    public TransactionManager transactionManager(){
        return new DataSourceTransactionManager(dataSource());
    }
}

业务类的注解与声明式事务中的注解一致