Spring(四)Spring與數據庫編程

  Spring最重要的功能毫無疑問就是操做數據。數據庫的百年城是互聯網編程的基礎,Spring爲開發者提供了JDBC模板模式,那就是它自身的JdbcTemplate。Spring還提供了TransactionTemplate支持事務的模板。Spring並無支持MyBatis,好在MyBatis社區開發了接入Spring的開發包,該包也提供了SqlSessionTemplate給開發者使用,該包還能夠屏蔽SqlSessionTemplate這樣的功能性代碼,能夠在編程中擦除SqlSessionTemplate讓開發者直接使用接口編程,大大提升了編碼的可讀性。java

  1、傳統JDBC代碼的弊端mysql

  例如,下面的代碼的做用是,經過JDBC讀取數據庫,而後將結果集以POJO的形式返回。spring

package com.ssm.chapter12.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.ssm.chapter12.pojo.Role;

public class JdbcExample {
    
    public Role getRole(Long id) {
        Role role = null;
        // 聲明JDBC變量
        Connection con = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 註冊驅動程序
            Class.forName("com.mysql.jdbc.Driver");
            // 獲取鏈接
            con = DriverManager.getConnection("jdbc:mysql://localhost:3306/chapter12", "root", "123456");
            // 預編譯SQL
            ps = con.prepareStatement("select id, role_name, note from t_role where id = ?");
            // 設置參數
            ps.setLong(1, id);
            // 執行SQL
            rs = ps.executeQuery();
            // 組裝結果集返回到POJO
            while (rs.next()) {
                role = new Role();
                role.setId(rs.getLong(1));
                role.setRoleName(rs.getString(2));
                role.setNote(rs.getString(3));
            }
        } catch (ClassNotFoundException | SQLException e) {
            // 異常處理
            e.printStackTrace();
        } finally {
            // 關閉數據庫鏈接資源
            try {
                if (rs != null && !rs.isClosed()) {
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (ps != null && !ps.isClosed()) {
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (con != null && !con.isClosed()) {
                    con.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return role;
    }
}
傳統的JDBC

  從代碼能夠看出,即便是執行一條簡單的SQL,其過程也不簡單,太多的try...catch...finally...語句,形成了代碼氾濫。sql

  在JDBC中,大量的JDBC代碼都是用於Chau給你姐愛你鏈接和語句以及異常處理的樣版代碼。數據庫

  實際上,這些樣版代碼是很是重要的。清理資源和處理錯誤確保了數據訪問的健壯性。若是沒有它們的話,就不會發現錯誤並且資源也會處於打開的狀態,這將會致使意外的代碼和資源泄露。咱們不只須要這些代碼,並且還要保證它是正確的。基於這樣的緣由,才須要框架來保證這些代碼只寫一次並且是正確的。apache

 

  2、使用Spring配置數據庫資源編程

  在Spring中配置數據庫資源很簡單,在實際工做中,大部分會配置成數據庫鏈接池,既能夠經過使用Spring內部提供的類,也可使用第三方數據庫鏈接池或者從Web服務器中經過JNDI獲取數據源。因爲使用了第三方的類,通常而言在工程中會偏向於採用XML的方式進行配置。緩存

  1.使用簡單數據庫配置安全

  Spring提供了一個類org.springframework.jdbc.datasource.SimpleDriverDataSource能夠支持簡單數據庫配置,可是不支持數據庫鏈接池。服務器

  這種配置通常用於測試,由於它不是一個數據庫鏈接池。

    <!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"> 
        <property name="username" value="root" /> <property name="password" value="123456" 
        /> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property 
        name="url" value="jdbc:mysql://localhost:3306/chapter12" /> </bean> -->

  2.使用第三方數據庫鏈接池

  當使用第三方數據庫鏈接池時,好比DBCP數據庫鏈接池,須要下載第三方包common-dbcp.jar和common-pool包,而後在Spring中簡單配置後,就可以使用它了。

    <!-- 數據庫鏈接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/chapter12" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <!--鏈接池的最大數據庫鏈接數 -->
        <property name="maxActive" value="255" />
        <!--最大等待鏈接中的數量 -->
        <property name="maxIdle" value="5" />
        <!--最大等待毫秒數 -->
        <property name="maxWait" value="10000" />
    </bean>

  3.使用JNDI數據庫鏈接池

  在Tomcat、WebLogic等Java EE服務器上配置數據源,這是他存在一個JNDI的名稱。也能夠經過Spring所提供的JNDI機制獲取對應的數據源,這也是經常使用的方式。

  假設在Tomcat上配置了JNDI爲jdbc/chapter12的數據源,這樣就能夠在Web工程中獲取這個JNDI數據源。

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" vaule="java:comp/env/jdbc/chapter12" />
    </bean>

 

  3、JDBC代碼失控的解決方案--JdbcTemplate

  JdbcTemplate是Spring針對JDBC代碼失控提供的解決方案,雖然不算成功,可是用技術提供模板化的編程,減小了開發者的工做量。

  Spring的JDBC框架承擔了資源管理和異常處理的工做,從而簡化了JDBC代碼,讓咱們只需編寫從數據庫讀寫數據的必須代碼。

  1.配置JdbcTemplate,其中dataSource在以前的三種方法中選一種便可

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource" />
    </bean>

  2.配置好了JdbcTemplate和dataSource就能夠操做JdbcTemplate了,假設Spring配置文件爲spring-cfg.xml,則要想完成第一個例子中JDBC完成的工做,只須要:

    public static void tesSpring() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
        Long id = 1L;
        String sql = "select id, role_name, note from t_role where id = " + id;
        Role role = jdbcTemplate.queryForObject(sql, new RowMapper<Role>() {
            @Override
            public Role mapRow(ResultSet rs, int rownum) throws SQLException {
                Role result = new Role();
                result.setId(rs.getLong("id"));
                result.setRoleName(rs.getString("role_name"));
                result.setNote(rs.getString("note"));
                return result;
            }
        });
        System.out.println(role.getRoleName());
    }

  其中,使用了jdbcTemplate的queryForObject方法,它包含了兩個參數,一個是SQL,另外一個是RowMapper接口。在mapRow()方法中,從ResultSet對象中取出查詢獲得的數據,組裝成一個Role對象,而無需再寫任何關閉數據庫資源的代碼。由於JdbcTemplate內部實現了它們,這即是Spring所提供的模板規則。

  3.JdbcTemplate的增、刪、改、查

package com.ssm.chapter12.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import com.ssm.chapter12.pojo.Role;

public class JdbcTemplateTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
        
        JdbcTemplateTest test = new JdbcTemplateTest();
        test.getRoleByConnectionCallback(jdbcTemplate, 1L);
        test.getRoleByStatementCallback(jdbcTemplate, 1L);
        test.insertRole(jdbcTemplate);
        List roleList = test.findRole(jdbcTemplate, "role");
        System.out.println(roleList.size());
        Role role = new Role();
        role.setId(1L);
        role.setRoleName("update_role_name_1");
        role.setNote("update_note_1");
        test.updateRole(jdbcTemplate, role);
        test.deleteRole(jdbcTemplate, 1L);
    }


    /***
     * 插入角色
     * @param jdbcTemplate --模板
     * @return 影響條數
     */
    public int insertRole(JdbcTemplate jdbcTemplate) {
        String roleName = "role_name_1";
        String note = "note_1";
        String sql = "insert into t_role(role_name, note) values(?, ?)";
        return jdbcTemplate.update(sql, roleName, note);
    }

    /**
     * 刪除角色
     * @param jdbcTemplate -- 模板
     * @param id -- 角色編號,主鍵
     * @return 影響條數
     */
    public int deleteRole(JdbcTemplate jdbcTemplate, Long id) {
        String sql = "delete from t_role where id=?";
        return jdbcTemplate.update(sql, id);
    }

    public int updateRole(JdbcTemplate jdbcTemplate, Role role) {
        String sql = "update t_role set role_name=?, note = ? where id = ?";
        return jdbcTemplate.update(sql, role.getRoleName(), role.getNote(), role.getId());
    }

    /**
     * 查詢角色列表
     * @param jdbcTemplate--模板
     * @param roleName --角色名稱
     * @return 角色列表
     */
    public List<Role> findRole(JdbcTemplate jdbcTemplate, String roleName) {
        String sql = "select id, role_name, note from t_role where role_name like concat('%',?, '%')";
        Object[] params = {roleName};//組織參數
        //使用RowMapper接口組織返回(使用lambda表達式)
        List<Role> list = jdbcTemplate.query(sql, params, (ResultSet rs, int rowNum) -> {
            Role result = new Role();
            result.setId(rs.getLong("id"));
            result.setRoleName(rs.getString("role_name"));
            result.setNote(rs.getString("note"));
            return result;
        });
        return list;
    }
    
    /**
     * 使用ConnectionCallback接口進行回調
     * @param jdbcTemplate 模板
     * @param id 角色編號
     * @return 返回角色
     */
    public Role getRoleByConnectionCallback(JdbcTemplate jdbcTemplate, Long id) {
        Role role = null;
        //這裏寫成Java 8的Lambda表達式,若是你使用低版本的Java,須要使用ConnectionCallback匿名類
        role = jdbcTemplate.execute((Connection con) -> {
            Role result = null;
            String sql = "select id, role_name, note from t_role where id = ?";
            PreparedStatement ps = con.prepareStatement(sql);
            ps.setLong(1, id);
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                result = new Role();
                result.setId(rs.getLong("id"));
                result.setNote(rs.getString("note"));
                result.setRoleName(rs.getString("role_name"));
            }
            return result;
        });
        return role;
    }

    /**
     * 使用StatementCallback接口進行回調
     * @param jdbcTemplate模板
     * @param id角色編號
     * @return返回角色
     */
    public Role getRoleByStatementCallback(JdbcTemplate jdbcTemplate, Long id) {
        Role role = null;
         //這裏寫成Java 8的lambda表達式,若是你使用低版本的Java,須要使用StatementCallback的匿名類
        role = jdbcTemplate.execute((Statement stmt) -> {
            Role result = null;
            String sql = "select id, role_name, note from t_role where id = " + id;
            ResultSet rs = stmt.executeQuery(sql);
            while (rs.next()) {
                result = new Role();
                result.setId(rs.getLong("id"));
                result.setNote(rs.getString("note"));
                result.setRoleName(rs.getString("role_name"));
            }
            return result;
        });
        return role;
    }
}
JdbcTemplate的增、刪、改、查

  4.執行多條SQL

  一個JdbcTemplate只執行了一條SQL,當須要屢次執行SQL時,可使用execute方法。它將容許傳遞ConnectionCallback或者StatementCallback等接口進行回調。

    /**
     * 使用ConnectionCallback接口進行回調
     * @param jdbcTemplate 模板
     * @param id 角色編號
     * @return 返回角色
     */
    public Role getRoleByConnectionCallback(JdbcTemplate jdbcTemplate, Long id) {
        Role role = null;
        //這裏寫成Java 8的Lambda表達式,若是你使用低版本的Java,須要使用ConnectionCallback匿名類
        role = jdbcTemplate.execute((Connection con) -> {
            Role result = null;
            String sql = "select id, role_name, note from t_role where id = ?";
            PreparedStatement ps = con.prepareStatement(sql);
            ps.setLong(1, id);
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                result = new Role();
                result.setId(rs.getLong("id"));
                result.setNote(rs.getString("note"));
                result.setRoleName(rs.getString("role_name"));
            }
            return result;
        });
        return role;
    }

    /**
     * 使用StatementCallback接口進行回調
     * @param jdbcTemplate模板
     * @param id角色編號
     * @return返回角色
     */
    public Role getRoleByStatementCallback(JdbcTemplate jdbcTemplate, Long id) {
        Role role = null;
         //這裏寫成Java 8的lambda表達式,若是你使用低版本的Java,須要使用StatementCallback的匿名類
        role = jdbcTemplate.execute((Statement stmt) -> {
            Role result = null;
            String sql = "select id, role_name, note from t_role where id = " + id;
            ResultSet rs = stmt.executeQuery(sql);
            while (rs.next()) {
                result = new Role();
                result.setId(rs.getLong("id"));
                result.setNote(rs.getString("note"));
                result.setRoleName(rs.getString("role_name"));
            }
            return result;
        });
        return role;
    }
執行多條SQL

 

  4、MyBatis-Spring項目

  目前大部分的互聯網項目中都使用SSM搭建平臺的。使用Spring IoC能夠有效管理各種Java資源,達到即插即拔的功能;經過AOP框架,數據庫事務能夠委託給Spring處理,消除很大一部分的事務代碼,配合MyBatis的高靈活、可配置、可優化SQL等特性,徹底能夠構建高性能的大型網站。  

  在Spring環境中使用MyBatis也更加簡單,節省了很多代碼,甚至能夠不用SqlSessionFactory、SqlSession等對象。由於MyBatis-Spring爲咱們封裝了它們。

  配置MyBatis-Spring項目須要下面幾步:

  1. 配置數據源
  2. 配置SqlSessionFactory
  3. 能夠選擇的配置由SqlSessionTemplate,在同時配置SqlSessionTemplate和SqlSessionFactory的狀況下,優先採用SqlSessionTemplate
  4. 配置Mapper,能夠配置單個Mapper,也能夠經過掃描的方法生成Mapper,比較靈活。此時Spring IoC會生成對應接口的實例,這樣就能夠經過注入的方式來獲取資源。
  5. 事務管理。

 

  1.配置SqlSessionFactory Bean

  MyBatis中SqlSessionFactory是產生SqlSession的基礎,所以配置SqlSessionFactory十分關鍵。在MyBatis-Spring項目中提供了SqlSessionFactoryBean支持SqlSessionFactory的配置。

  (1)在Spring的配置文件spring-cfg.xml中配置SqlSessionFactoryBean

    這裏雖然只是配置了數據源,而後引入了一個MyBatis配置文件,這樣的好處在於不至於使得SqlSessionFactoryBean的配置所有依賴於Spring提供的規則,致使配置的複雜性。

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:sqlMapConfig.xml" />
    </bean>

  (2)引入的MyBatis配置文件sqlMapConfig.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="cacheEnabled" value="true" />
        <!-- 容許 JDBC 支持生成的鍵。須要適合[修改成:適當]的驅動。若是設置爲true,則這個設置強制生成的鍵被使用,儘管一些驅動拒絕兼容但仍然有效(好比 Derby) -->
        <setting name="useGeneratedKeys" value="true" />
        <!-- 配置默認的執行器。SIMPLE 執行器沒有什麼特別之處。REUSE 執行器重用預處理語句。BATCH 執行器重用語句和批量更新  -->
        <setting name="defaultExecutorType" value="REUSE" />
        <!-- 全局啓用或禁用延遲加載。當禁用時,全部關聯對象都會即時加載 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 設置超時時間,它決定驅動等待一個數據庫響應的時間  -->
        <setting name="defaultStatementTimeout" value="25000"/> 
    </settings>

<!-- 別名配置 --> <typeAliases> <typeAlias alias="role" type="com.ssm.chapter12.pojo.Role" /> </typeAliases> <!-- 指定映射器路徑 --> <mappers> <mapper resource="com/ssm/chapter12/sql/mapper/RoleMapper.xml" /> </mappers> </configuration>

  (3)而後引入映射器RoleMapper.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="com.ssm.chapter12.mapper.RoleMapper">

    <insert id="insertRole" useGeneratedKeys="true" keyProperty="id">
        insert into t_role(role_name, note) values (#{roleName}, #{note})
    </insert>

    <delete id="deleteRole" parameterType="long">
        delete from t_role where id=#{id}
    </delete>

    <select id="getRole" parameterType="long" resultType="role">
        select id, role_name as roleName, note from t_role where id = #{id}
    </select>

    <update id="updateRole" parameterType="role">
        update t_role
        set role_name = #{roleName},
        note = #{roleName}
        where id = #{id}
    </update>
</mapper>

  (4)與映射器配置文件對應的接口類java文件RoleMapper.java

package com.ssm.chapter12.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import com.ssm.chapter12.pojo.Role;

public interface RoleMapper {
    public int insertRole(Role role);
    public Role getRole(@Param("id") Long id);
    public int updateRole(Role role);
    public int deleteRole(@Param("id") Long id);
}

  至此,MyBatis框架的主要代碼就已經配置完成了,可是,因爲RoleMapper是一個接口,而不是一個類,它沒有辦法產生示例,所以應該如何配置呢?

  

  2.SqlSessionTemplate組件

  SqlSessionTemplate並非一個必需配置的組件,可是它也存在必定的價值。首先,它是線程安全的類,也就是確保每一個線程使用的SqlSession惟一且不互相沖突。其次,它提供了一系列的功能,好比增、刪、改、查等經常使用功能。

  配置方法以下:SqlSessionTemplate類要經過帶有參數的構造方法去建立對象,經常使用的參數是sqlSessionFactory和MyBatis執行器(Executor)類型,取值範圍是SIMPLE、REUSE、BATCH。

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory" />
        <!-- <constructor-arg value="BATCH"/> -->
    </bean>

  SqlSessionTemplate配置完成就可使用它了,例如:

    public static void testSqlSessionTemplate() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        // ctx爲Spring IoC容器
        SqlSessionTemplate sqlSessionTemplate = ctx.getBean(SqlSessionTemplate.class);
        Role role = new Role();
        role.setRoleName("role_name_sqlSessionTemplate");
        role.setNote("note_sqlSessionTemplate");
        sqlSessionTemplate.insert("com.ssm.chapter12.mapper.RoleMapper.insertRole", role);
        Long id = role.getId();
        sqlSessionTemplate.selectOne("com.ssm.chapter12.mapper.RoleMapper.getRole", id);
        role.setNote("update_sqlSessionTemplate");
        sqlSessionTemplate.update("com.ssm.chapter12.mapper.RoleMapper.updateRole", role);
        sqlSessionTemplate.delete("com.ssm.chapter12.mapper.RoleMapper.deleteRole", id);
    }

  運行結果:從結果中能夠看到,每運行一個SqlSessionTemplate時,它就會從新獲取一個新的SqlSession,也就是說每個SqlSessionTemplate運行的時候會產生新的SqlSession,因此每個方法都是獨立的SqlSession,這意味着它是安全的線程。

  SqlSessionTemplate目前運用已經很少,它須要使用字符串代表運行哪一個SQL,字符串包含業務含義,只是功能性代碼,並不符合面向對象的規範。與此同時,使用字符串時,IDE沒法檢查代碼邏輯的正確性,因此這樣的用法漸漸被人們拋棄了。可是,SqlSessionTemplate容許配置執行器的類型,當同時配置SqlSessionTemplate和SqlSessionFactory時,優先採用SqlSessionTemplate。

DEBUG 2018-10-09 17:32:51,048 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG 2018-10-09 17:32:51,052 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@38102d01] was not registered for synchronization because synchronization is not active
DEBUG 2018-10-09 17:32:51,065 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource
DEBUG 2018-10-09 17:32:51,329 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL Connector Java] will not be managed by Spring
DEBUG 2018-10-09 17:32:51,333 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?) 
DEBUG 2018-10-09 17:32:51,367 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_sqlSessionTemplate(String), note_sqlSessionTemplate(String)
DEBUG 2018-10-09 17:32:51,372 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2018-10-09 17:32:51,375 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@38102d01]
DEBUG 2018-10-09 17:32:51,375 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
DEBUG
2018-10-09 17:32:51,375 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession DEBUG 2018-10-09 17:32:51,375 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16610890] was not registered for synchronization because synchronization is not active DEBUG 2018-10-09 17:32:51,377 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource DEBUG 2018-10-09 17:32:51,378 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL Connector Java] will not be managed by Spring DEBUG 2018-10-09 17:32:51,378 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select id, role_name as roleName, note from t_role where id = ? DEBUG 2018-10-09 17:32:51,378 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 7(Long) DEBUG 2018-10-09 17:32:51,390 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 1 DEBUG 2018-10-09 17:32:51,393 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16610890] DEBUG 2018-10-09 17:32:51,393 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
DEBUG
2018-10-09 17:32:51,393 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession DEBUG 2018-10-09 17:32:51,393 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6283d8b8] was not registered for synchronization because synchronization is not active DEBUG 2018-10-09 17:32:51,393 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource DEBUG 2018-10-09 17:32:51,394 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL Connector Java] will not be managed by Spring DEBUG 2018-10-09 17:32:51,394 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: update t_role set role_name = ?, note = ? where id = ? DEBUG 2018-10-09 17:32:51,394 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_sqlSessionTemplate(String), role_name_sqlSessionTemplate(String), 7(Long) DEBUG 2018-10-09 17:32:51,397 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1 DEBUG 2018-10-09 17:32:51,397 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6283d8b8] DEBUG 2018-10-09 17:32:51,397 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
DEBUG
2018-10-09 17:32:51,397 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession DEBUG 2018-10-09 17:32:51,397 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1da2cb77] was not registered for synchronization because synchronization is not active DEBUG 2018-10-09 17:32:51,397 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource DEBUG 2018-10-09 17:32:51,398 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL Connector Java] will not be managed by Spring DEBUG 2018-10-09 17:32:51,398 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: delete from t_role where id=? DEBUG 2018-10-09 17:32:51,398 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 7(Long) DEBUG 2018-10-09 17:32:51,400 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1 DEBUG 2018-10-09 17:32:51,400 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1da2cb77] DEBUG 2018-10-09 17:32:51,400 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource

 

  3.配置MapperFactory Bean

  MyBatis的運行只須要提供相似於RoleMapper.java的接口,而無需提供一個實現類。而根據MyBatis的運行原理,它是由MyBatis體系建立的動態代理對象運行的,因此Spring也沒有辦法爲其生成一個實現類。爲了解決這個問題,MyBatis-Spring項目提供了一個MapperFactoryBean類做爲中介,能夠經過配置這個類來實現想要的Mapper。使用了Mapper接口編程方式能夠有效地在邏輯代碼中擦除SqlSessionTemplate,這樣代碼就按照面向對象的規範進行編寫了。

  配置RoleMapper對象:

    <bean id="roleMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> 
        <property name="mapperInterface" value="com.ssm.chapter12.mapper.RoleMapper" />
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
    </bean>

  有三個屬性:

  • mapperInterface
  • sqlSessionFactory
  • SqlSessionTemplate

  其中,若是同時配置sqlSessionFactory和SqlSessionTemplate,那麼就會啓用sqlSessionFactory,而SqlSessionTemplate做廢。

  能夠經過RoleMapper roleMapper = ctx.getBean(RoleMapper.class);來獲取映射器

    public static void testRoleMapper() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        RoleMapper roleMapper = ctx.getBean(RoleMapper.class);
        roleMapper.getRole(2L);
    }

  

  4.配置MapperScannerConfigurer

  在項目比較大的狀況下,若是一個個配置Mapper會形成配置量大的問題,這顯然不利於開發,所以可使用MapperScannerConfigurer類來用掃描的形式去生產對應的Mapper。

  在Spring配置前須要給Mapper一個註解,在Spring中每每是使用@Repository表示DAO層的,

package com.ssm.chapter12.mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository; import com.ssm.chapter12.pojo.Role;
@Repository
public interface RoleMapper { public int insertRole(Role role); public Role getRole(@Param("id") Long id); public int updateRole(Role role); public int deleteRole(@Param("id") Long id); }

  而後在Spring配置文件中進行配置:在配置中:

    第一行:basePackage指定讓Spring自動掃描的包,它會逐層深刻掃描,若是遇到多個包可使用半角逗號分隔。

    第二行:指定在Spring中定義的sqlSessionFactory的Bean名稱。

    第三行:若是類被annotationClass聲明的註解標識的時候,才進行掃描。這裏是只將被@Repository註解的接口類註冊成對應的Mapper。

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.ssm.chapter12.mapper" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!-- 使用sqlSessionTemplateBeanName將覆蓋sqlSessionFactoryBeanName的配置 -->
        <!-- <property name="sqlSessionTemplateBeanName" value="sqlSessionFactory"/> -->
        <!-- 指定標註才掃描成爲Mapper -->
        <property name="annotationClass" value="org.springframework.stereotype.Repository" />
    </bean>

  

  5.測試Spirng+Mybatis

  通過上面的概括認識,整理出一份標準的XML配置文件:包括dataSourcesqlSessionFactory和MapperScannerConfigurer

<?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-4.0.xsd">

    <!-- 數據庫鏈接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/chapter6?useSSL=false" />
        <property name="username" value="root" />
        <property name="password" value="bjtungirc" />
        <!--鏈接池的最大數據庫鏈接數 -->
        <property name="maxActive" value="255" />
        <!--最大等待鏈接中的數量 -->
        <property name="maxIdle" value="5" />
        <!--最大等待毫秒數 -->
        <property name="maxWait" value="10000" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:sqlMapConfig.xml" />
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.ssm.chapter12.mapper" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!-- 使用sqlSessionTemplateBeanName將覆蓋sqlSessionFactoryBeanName的配置 -->
        <!-- <property name="sqlSessionTemplateBeanName" value="sqlSessionFactory"/> -->
        <!-- 指定標註才掃描成爲Mapper -->
        <property name="annotationClass" value="org.springframework.stereotype.Repository" />
    </bean>
</beans>

  驗證方法:

    public static void testMybatisSpring() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg.xml");
        // ctx爲Spring IoC容器
        RoleMapper roleMapper = ctx.getBean(RoleMapper.class);
        Role role = new Role();
        role.setRoleName("role_name_mapper");
        role.setNote("note_mapper");
        roleMapper.insertRole(role);
        Long id = role.getId();
        roleMapper.getRole(id);
        role.setNote("note_mapper_update");
        roleMapper.updateRole(role);
        roleMapper.deleteRole(id);
    }

  輸出結果:從日誌中能夠看出每當使用一個RoleMapper接口的方法嗎,它就會產生一個新的SqlSession,運行完成後就會自動關閉。

  從關閉的日誌Closing non transactional SqlSession中能夠看出是在一個非事務的場景下運行,因此這裏並不完整,只是簡單地使用了數據庫,並無啓動數據庫事務。

DEBUG 2018-10-09 18:17:36,687 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG 2018-10-09 18:17:36,692 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10d68fcd] was not registered for synchronization because synchronization is not active
DEBUG 2018-10-09 18:17:36,697 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource
DEBUG 2018-10-09 18:17:36,937 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL Connector Java] will not be managed by Spring
DEBUG 2018-10-09 18:17:36,942 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==>  Preparing: insert into t_role(role_name, note) values (?, ?) 
DEBUG 2018-10-09 18:17:36,964 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_mapper(String), note_mapper(String)
DEBUG 2018-10-09 18:17:36,968 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==    Updates: 1
DEBUG 2018-10-09 18:17:36,971 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@10d68fcd]
DEBUG 2018-10-09 18:17:36,971 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
DEBUG
2018-10-09 18:17:36,973 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession DEBUG 2018-10-09 18:17:36,973 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@169e6180] was not registered for synchronization because synchronization is not active DEBUG 2018-10-09 18:17:36,974 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource DEBUG 2018-10-09 18:17:36,975 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL Connector Java] will not be managed by Spring DEBUG 2018-10-09 18:17:36,975 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select id, role_name as roleName, note from t_role where id = ? DEBUG 2018-10-09 18:17:36,975 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 8(Long) DEBUG 2018-10-09 18:17:36,985 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 1 DEBUG 2018-10-09 18:17:36,987 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@169e6180] DEBUG 2018-10-09 18:17:36,987 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
DEBUG
2018-10-09 18:17:36,988 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession DEBUG 2018-10-09 18:17:36,988 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fb3ee4e] was not registered for synchronization because synchronization is not active DEBUG 2018-10-09 18:17:36,988 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource DEBUG 2018-10-09 18:17:36,988 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL Connector Java] will not be managed by Spring DEBUG 2018-10-09 18:17:36,988 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: update t_role set role_name = ?, note = ? where id = ? DEBUG 2018-10-09 18:17:36,989 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_mapper(String), role_name_mapper(String), 8(Long) DEBUG 2018-10-09 18:17:36,990 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1 DEBUG 2018-10-09 18:17:36,991 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4fb3ee4e] DEBUG 2018-10-09 18:17:36,991 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource
DEBUG
2018-10-09 18:17:36,991 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession DEBUG 2018-10-09 18:17:36,991 org.mybatis.spring.SqlSessionUtils: SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2c35e847] was not registered for synchronization because synchronization is not active DEBUG 2018-10-09 18:17:36,991 org.springframework.jdbc.datasource.DataSourceUtils: Fetching JDBC Connection from DataSource DEBUG 2018-10-09 18:17:36,992 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL Connector Java] will not be managed by Spring DEBUG 2018-10-09 18:17:36,992 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: delete from t_role where id=? DEBUG 2018-10-09 18:17:36,992 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 8(Long) DEBUG 2018-10-09 18:17:36,994 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1 DEBUG 2018-10-09 18:17:36,994 org.mybatis.spring.SqlSessionUtils: Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2c35e847] DEBUG 2018-10-09 18:17:36,994 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource

 

  5、數據庫的相關知識

  1.數據庫事務ACID特性

  • 原子性(Atomicity):整個事務中的全部操做,要麼所有完成,要麼所有不完成,不可能停滯在中間某個環節。事務在執行過程當中發生錯誤,會被回滾到事務開始前的狀態,就像整個事務歷來沒被執行過同樣。
  • 一致性(Consistency):指一個事務能夠改變封裝狀態(除非它是一個只讀的)。事務必須始終保持系統處於一致性的狀態,無論在任何給定的時間併發事務有多少。
  • 隔離性(Isolation):指兩個事務之間的隔離程度
  • 持久性(Durability):在事務完成之後,該事物對數據庫所作的更改便持久保存在數據庫之中了,並不會被回滾。

  

  2.丟失更新

  在互聯網中存在着搶購、秒殺等高併發場景,使得數據庫在一個多事務的環境中運行,多個事務的併發會產生一系列的問題,主要的問題之一就是丟失更新,丟失更新分爲兩類:

  假設一個帳戶同時存在互聯網消費和刷卡消費兩種形式,而一對夫妻共同使用這個帳戶。

  • 第一類丟失更新

    在最後的T6時刻,老婆回滾事務,卻恢復了原來的初始值餘額10000元,可是老公已經消費了1000元,這顯然是不對的。

    這樣的兩個事務併發,一個回滾,一個提交成功緻使不一致,稱爲第一類丟失更新。大部分數據庫基本都已經消滅了這類丟失更新。

  • 第二類丟失更新

  

  兩個事務併發,二者都提交了事務,因爲在不一樣的事務中,沒法探知其餘事務的操做,致使不一致,稱爲第二類丟失更新。

  爲了克服第二類丟失更新即保證事務之間協助的一致性,數據庫中頂一個事務之間的隔離級別,來不一樣程度上減小出現丟失更新的可能。

 

  3.隔離級別

  按照SQL的標準規範,把隔離級別定義爲4層:髒讀(dirty read)、讀/寫提交(read commit)、可重複讀(repeatable read)和序列化(Serializable)

  各種的隔離級別和產生的現象:

  

  (1)髒讀(dirty read)  

    髒讀是最低的隔離級別,其含義是容許一個事務去讀取另外一個事務中未提交的數據。

    

  在T3時刻老婆啓動了消費,致使餘額爲9000元,老公在T4時刻消費,由於用了髒讀,因此可以讀取老婆消費後的餘額(這個餘額是事務二未提交的)爲9000元,這樣餘額就爲8000元,而後T5時刻老公提交了事務,餘額變成了8000元。

  可是,老婆在T6時刻回滾事務,因爲數據庫已經克服了第一類丟失更新,因此餘額依舊爲8000元。

  這是因爲,事務一能夠讀取事務二未提交的事務,這樣的場景被稱爲髒讀。

   (2)讀/寫提交

  爲了克服髒讀,SQL提出了第二個隔離級別--讀/寫提交。讀/寫提交,就是一個事務只能讀取另外一個事務已經提交的數據。

  

  在T3時刻,因爲事務採起讀/寫提交的隔離級別,因此老公沒法讀取老婆未提交的9000元餘額,只能讀到10000元的餘額,因而在T5提交事務後餘額變爲9000元。而T6時刻老婆回滾事務,結果也是正確的9000元。

  髒讀能夠引起其餘的問題:

  

  因爲T7時刻事務一知道事務二提交的結果--餘額爲1000元,致使老公沒有錢買單。對於老公而言,他並不知道老婆作了什麼事情,可是帳戶餘額卻莫名其妙地從10000元變爲了1000元,對他來講帳戶餘額是不能重複讀取的,而是一個會變化的值,這樣的場景稱爲不可重複讀,這是讀/寫提交存在的問題。

  (3)可重複讀

  可重複讀是針對數據庫同一條記錄而言的,便可重複讀會使得同一條數據庫記錄的讀/寫按照一個序列化進行操做,不會產生交叉狀況,這樣就能保證同一條數據的一致性,進而保證上述場景的正確性。可是因爲數據庫並非只能針對一條記錄進行讀/寫操做,在不少場景,數據庫須要同時對多條記錄進行讀/寫,這個時候就會產生幻讀。  

  按照下面的例子,可重複讀的意思就是,在T一、T二、T3和T4時刻,都只有一條操做,也就是操做的序列化。

  

  可是,老婆在T1查詢到10條記錄,到T4打印記錄時,並不知道老公在T2和T3時刻進行了消費,致使多一條(可重複讀是針對同一條記錄而言的,而這裏不是同一條記錄)消費記錄的產生,她會質疑這條多出來的記錄是否是不存在的,這樣的場景稱爲幻讀。

  (4)序列化

  爲了克服幻讀,SQL又提出了序列化的隔離級別。它是一種讓SQL按照順序讀/寫的方式,可以消除數據庫事務之間併發產生數據不一致的問題。

  

  4.傳播行爲

  傳播行爲是指方法之間的調用事務策略的問題。在大部分的狀況下,咱們都但願事務可以同時成功或者同時失敗。可是也會有例外,假設如今須要信用卡的還款功能,有一個總的調用代碼邏輯--RepaymentBatchService的batch方法,那麼它要實現的是記錄還款成功的總卡數和對應完成的信息,而每一張卡的還款則是經過RepaymentService的repay方法完成的。

  

  若是隻有一條業務,那麼當調用repay方法對某一張信用卡進行還款時,若是發生了異常,若是將這條事務回滾,就會形成全部的數據操做都會被回滾,那些已經正常還款的用戶也會還款失敗。

  可是,若是batch方法調用repay方法時,它會爲repay方法建立一條新的事務。當這個方法產生異常時,只會回滾它自身的事務,而不會影響主事務和其餘事務,這樣就能避免上面遇到的問題。

  一個方法調用另一個方法時,能夠對事務的特性進行傳播配置,稱爲傳播行爲。

 

  5.選擇隔離級別和傳播行爲

(1)選擇隔離級別

  在互聯網應用中,不但要考慮數據庫的一致性,還要考慮系統的性能。通常而言,從髒讀到序列化,系統性能直線降低。所以設置高的級別,好比序列化,會嚴重壓制併發,從而引起大量的線程掛起,直到得到鎖才能進一步操做,而恢復時有須要大量的等待時間。大部分場景下,企業會選擇讀/寫提交的方式設置事務,這樣既有助於提升併發,又壓制了髒讀,可是對於數據一致性問題並無解決。

  並非全部的業務都在高併發下完成,當業務併發量不是很大或者根本不須要考慮的狀況下,使用序列化隔離級別用以保證數據的一致性,也是一個不錯的選擇。

  在實際工做中,@Transactional隔離級別的默認值爲Isolation.DEFAULT,隨數據庫默認值的變化而變化,必須MySQl支持4種隔離級別,默認的是可重複讀的隔離級別;而Oracle只能支持讀/寫提交和序列化兩種隔離級別,默認爲讀/寫提交。

  

  (2)選擇傳播行爲

  在Spring中傳播行爲的類型,是經過一個枚舉類型定義的,這個枚舉類是org.springframework.transaction.annotation.Propagation,其中定義了七種傳播行爲:

  最經常使用的是REQUIRED,也是默認的傳播行爲。

  

    

  

  6、Spring數據庫事務管理

  數據庫事務是企業應用最爲重要的內容之一,與之密切關聯的就是Spring中最著名的註解之一--@Transactional註解。

  互聯網系統時時面對着高併發,在互聯網系統中同時跑着成百上千條線程都是十分常見的,致使數據庫在一個多事務訪問的環境中,從而引起數據庫丟失更新和數據一致性的問題,同時也會給服務器帶來很大壓力,甚至發生數據庫系統死鎖和癱瘓進而致使系統宕機。

  在大部分狀況下,咱們會認爲數據庫事務要麼同時成功,要麼同時失敗,可是也存在着不一樣的要求。好比銀行的信用卡還款,有個跑批量的事務,而這個批量事務又包含了對各個信用卡的還款業務的處理,我哦們補鞥由於其中一張卡的事務失敗了,而把其餘卡的事務也回滾,這樣就會致使由於一個客戶的異常,形成多個客戶還款失敗,即正常還款的用戶,也被認爲是不正常的還款,這樣會引起嚴重的金融信譽問題,Spring事務帶來了比較方便的解決方案。

  1.Spring數據庫事務管理器的設計

  在Spring中的數據庫事務是經過PlatformTransactionManager進行管理的,在以前已經知道JdbcTemplate是沒法支持事務的,而可以支持事務的是org.springframework.transaction.support.TranscctionTemplate模板,它是Spring所提供的事務管理器的模板。

  經過閱讀TranscctionTemplate的源碼,能夠發現事務的建立、提交和回滾都是經過PlatformTransactionManager接口來完成的;而且當事務產生異常時會回滾事務,在默認的實現中全部的異常都會回滾;當無異常時,會提交事務。

  在Spring中,有多種事務管理器:

  

  經常使用的是DataSourceTransactionManager,它繼承抽象事務管理器AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager又實現了PlatformTransactionManager接口。

  (1)配置事務管理器

  首先定義了數據庫鏈接池,而後使用DataSourceTransactionManager去定義數據庫事務管理器,而且注入了數據庫鏈接池。這樣Spring就知道你已經將數據庫事務委託給事務管理器transactionManager管理了。

    <!-- 數據庫鏈接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/chapter13"/>
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="maxActive" value="255" />
        <property name="maxIdle" value="5" />
        <property name="maxWait" value="10000" />
    </bean>

    <!-- 事務管理器配置數據源事務 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

  2.聲明式事務

  聲明式事務是一種約定型的事務,在大部分狀況下,當使用數據庫事務時,大部分的場景是在代碼中發生了異常時,須要回滾事務,而不發生異常時則提交事務,從而保證數據庫數據的一致性。從這點出發,Spring給了一個約定,若是使用的是聲明式事務,那麼當你的業務方法不發生異常時(或者發生異常,但該異常也被配置信息容許提交事務),Spring就會讓事務管理器提交事務,而發生異常(而且該異常不被你的配置信息所容許提交事務)時,則讓事務管理器回滾事務。

  聲明式事務容許自定義事務接口--TransactionDefinition,它能夠由XML或者註解@Transactional進行配置。

  Transactional配置項:propagation表示傳播行爲,isolation表示隔離級別。這些屬性會被Spring放到事務定義類TransactionDefinition中。

  事務定義器TransactionDefinition類中,將REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER和NESTED七個隔離級別分別設置爲常量0-6

  使用聲明式事務須要配置註解驅動,只須要加入下面的配置就可使用@Transactional配置事務了。

    <!-- 使用註解定義事務 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

  3.聲明式事務的約定流程

  @Transaction註解可使用在方法或類上面,在Spring IoC容器初始化時,Spring會讀入這個註解或者XML配置的事務信息,而且保存到一個事務定義類裏面(TransactionDefinition接口的子類),以備未來使用。當運行時會讓Spring攔截註解標註的某一個方法或類的全部方法。Spring利用AOP將代碼織入到AOP流程中,而後給出它的約定。

  

  約定流程爲:首先Spring經過事務管理器(PlatformTransactionManager的子類)建立事務,與此同時會把事務定義中的隔離級別、超時時間等屬性根據配置內容往事務上設置。而根據傳播行爲配置採起一種特定的策略,只需配置,無須編碼。而後,啓動開發者提供的業務代碼,Spring會經過反射的方式調度開發者的業務代碼,可是反射的結果多是正常返回或者產生異常的返回,那麼它給的約定是隻要發生異常,而且符合事務定義類的回滾條件,Spring就會將數據庫事務回滾,不然將數據庫事務提交,這也會Spring本身完成的。

  例如:下面的代碼中,只需在insertRole方法上使用@Transactional註解就能夠完成數據庫事務。

  對比於JDBC代碼,這裏沒有數據庫資源的打開和釋放代碼,也沒有數據庫提交的代碼,只有註解@Transactional。

  這樣就能夠實現,當insertRole方法拋出異常時,Spring就會回滾事務,若是成功,就提交事務。

  這裏的實現原理是Spring AOP技術,而其底層的實現原理是動態代理。

    @Autowired
    private RoleMapper roleMapper = null;

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public int insertRole(Role role) {
        return roleMapper.insertRole(role);
    }

 

  7、在Spring+MyBatis中使用數據庫事務

  

  1.運行環境XML配置

  首先配置Spring+MyBatis環境,即Spring配置文件spring-cfg.xml

<?xml version='1.0' encoding='UTF-8' ?>
<!-- was: <?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:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <!--啓用掃描機制,並指定掃描對應的包-->
    <context:annotation-config />
    <context:component-scan base-package="com.ssm.chapter13.*" />
    <!-- 數據庫鏈接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/chapter13"/>
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="maxActive" value="255" />
        <property name="maxIdle" value="5" />
        <property name="maxWait" value="10000" />
    </bean>

    <!-- 集成MyBatis -->
    <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
         <!--指定MyBatis配置文件-->
        <property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" />
    </bean>

    <!-- 事務管理器配置數據源事務 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 使用註解定義事務 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

    <!-- 採用自動掃描方式建立mapper bean -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       <property name="basePackage" value="com.ssm.chapter13" />
       <property name="SqlSessionFactory" ref="SqlSessionFactory" />
       <property name="annotationClass" value="org.springframework.stereotype.Repository" />
    </bean>
    
</beans>

  分析:

    dataSourceSqlSessionFactoryMapperScannerConfigurer用來支持Spring+Mybatis

    transactionManager是爲了配置事務管理器,同時將dataSource數據庫鏈接池注入到事務管理器

    tx:annotation-driven是爲了配置註解驅動,這樣纔可以使用@Transactional註解配置事務

  2.MyBatis相關配置

  數據庫表映射的POJO類Role.java

package com.ssm.chapter13.pojo;

public class Role {
    private Long id;
    private String roleName;
    private String note;
  
    /**getter and setter**/
}

  與之對應的是MyBatis映射文件mybatis-config.xml,創建SQL與POJO的映射關係:這裏只配置了一個簡單的映射器Mapper,須要配置一個接口就能夠了

<?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>
    <mappers>
        <mapper resource="com/ssm/chapter13/sqlMapper/RoleMapper.xml"/>
    </mappers>
</configuration>

  映射器Mapper文件RoleMapper.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="com.ssm.chapter13.mapper.RoleMapper">
    <insert id="insertRole" parameterType="com.ssm.chapter13.pojo.Role">
        insert into t_role (role_name, note) values(#{roleName}, #{note})
    </insert>
</mapper>

  與之對應,還須要有一個RoleMapper接口:

package com.ssm.chapter13.mapper;

import com.ssm.chapter13.pojo.Role;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleMapper {
    public int insertRole(Role role);
}

  3.服務(Service)類:

  業務接口1:RoleService.java。

package com.ssm.chapter13.service;
import com.ssm.chapter13.pojo.Role;

public interface RoleService {
    public int insertRole(Role role);  
}

  業務接口2:RoleListService.java。其中的insertRoleList方法能夠對角色列表進行插入。

package com.ssm.chapter13.service;

import java.util.List;

import com.ssm.chapter13.pojo.Role;

public interface RoleListService {
    public int insertRoleList(List<Role> roleList);
}

  業務實現類1:insertRole方法能夠對單個角色進行插入。其隔離級別設置爲讀/寫提交,傳播行爲爲REQUIRES_NEW,表示不管是否在當前事務,方法都會在新的事務中運行。

package com.ssm.chapter13.service.impl;

@Service
public class RoleServiceImpl implements RoleService {

    @Autowired
    private RoleMapper roleMapper = null;
    
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public int insertRole(Role role) {
        return roleMapper.insertRole(role);
    }
}

  業務實現類2:insertRoleList方法調用了RoleService接口的insertRole方法,能夠對角色列表進行插入。其隔離級別設置爲讀/寫提交,傳播行爲設置爲REQUIRE,表示當方法調用時,若是不存在當前事務,那麼就建立事務;若是以前的方法已經存在了事務,那麼就沿用以前的事務。

package com.ssm.chapter13.service.impl;

@Service
public class RoleListServiceImpl implements RoleListService {
    @Autowired
    private RoleService roleService = null;
    Logger log = Logger.getLogger(RoleListServiceImpl.class);
    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
    public int insertRoleList(List<Role> roleList) {
        int count = 0;
        for (Role role : roleList) {
            try {
                count += roleService.insertRole(role);
            } catch (Exception ex) {
                log.info(ex);
            }
        }
        return count;
    }
}

  4.測試類 

package com.ssm.chapter13.main;
public class Chapter13Main {
    public static void main(String [] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext ("spring-cfg.xml");
        RoleListService roleListService = ctx.getBean(RoleListService. class);
        List<Role> roleList = new ArrayList<Role>();
        for (int i=1; i<=2; i++) {
            Role role = new Role();
            role.setRoleName("role_name_" + i);
            role.setNote("note_" + i);
            roleList.add(role);
        }
        int count = roleListService.insertRoleList(roleList);
       System.out.println(count);
    }
}

  5.測試結果


 DEBUG 2018-10-09 23:21:29,550 org.springframework.transaction.support.AbstractPlatformTransactionManager: Creating new transaction with name [com.ssm.chapter13.service.impl.RoleListServiceImpl.insertRoleList]: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''
 DEBUG 2018-10-09 23:21:29,763 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] for JDBC transaction
 DEBUG 2018-10-09 23:21:29,766 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 2
 DEBUG 2018-10-09 23:21:29,767 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to manual commit
DEBUG 2018-10-09 23:21:29,767 org.springframework.transaction.support.AbstractPlatformTransactionManager: Suspending current transaction, creating new transaction with name [com.ssm.chapter13.service.impl.RoleServiceImpl.insertRole] DEBUG 2018-10-09 23:21:29,782 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] for JDBC transaction DEBUG 2018-10-09 23:21:29,782 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 2 DEBUG 2018-10-09 23:21:29,783 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to manual commit
DEBUG 2018-10-09 23:21:29,787 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession DEBUG 2018-10-09 23:21:29,791 org.mybatis.spring.SqlSessionUtils: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6] DEBUG 2018-10-09 23:21:29,796 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] will be managed by Spring DEBUG 2018-10-09 23:21:29,800 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: insert into t_role (role_name, note) values(?, ?) DEBUG 2018-10-09 23:21:29,824 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_1(String), note_1(String) DEBUG 2018-10-09 23:21:29,826 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1 DEBUG 2018-10-09 23:21:29,827 org.mybatis.spring.SqlSessionUtils: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6] DEBUG 2018-10-09 23:21:29,827 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6] DEBUG 2018-10-09 23:21:29,827 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6] DEBUG 2018-10-09 23:21:29,827 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6]
DEBUG
2018-10-09 23:21:29,827 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit DEBUG 2018-10-09 23:21:29,828 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] DEBUG 2018-10-09 23:21:29,830 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 4 DEBUG 2018-10-09 23:21:29,831 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] after transaction DEBUG 2018-10-09 23:21:29,831 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource DEBUG 2018-10-09 23:21:29,832 org.springframework.transaction.support.AbstractPlatformTransactionManager: Resuming suspended transaction after completion of inner transaction
DEBUG
2018-10-09 23:21:29,832 org.springframework.transaction.support.AbstractPlatformTransactionManager: Suspending current transaction, creating new transaction with name [com.ssm.chapter13.service.impl.RoleServiceImpl.insertRole] DEBUG 2018-10-09 23:21:29,832 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] for JDBC transaction DEBUG 2018-10-09 23:21:29,833 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 2 DEBUG 2018-10-09 23:21:29,834 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to manual commit
DEBUG 2018-10-09 23:21:29,834 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession DEBUG 2018-10-09 23:21:29,835 org.mybatis.spring.SqlSessionUtils: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7b50df34] DEBUG 2018-10-09 23:21:29,835 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] will be managed by Spring DEBUG 2018-10-09 23:21:29,835 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: insert into t_role (role_name, note) values(?, ?) DEBUG 2018-10-09 23:21:29,836 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_2(String), note_2(String) DEBUG 2018-10-09 23:21:29,836 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1 DEBUG 2018-10-09 23:21:29,836 org.mybatis.spring.SqlSessionUtils: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7b50df34] DEBUG 2018-10-09 23:21:29,837 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7b50df34] DEBUG 2018-10-09 23:21:29,837 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7b50df34] DEBUG 2018-10-09 23:21:29,837 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7b50df34]
DEBUG
2018-10-09 23:21:29,837 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit DEBUG 2018-10-09 23:21:29,837 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] DEBUG 2018-10-09 23:21:29,839 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 4 DEBUG 2018-10-09 23:21:29,840 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] after transaction DEBUG 2018-10-09 23:21:29,840 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource DEBUG 2018-10-09 23:21:29,840 org.springframework.transaction.support.AbstractPlatformTransactionManager: Resuming suspended transaction after completion of inner transaction DEBUG 2018-10-09 23:21:29,840 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit DEBUG 2018-10-09 23:21:29,841 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] DEBUG 2018-10-09 23:21:29,841 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 4 DEBUG 2018-10-09 23:21:29,842 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] after transaction DEBUG 2018-10-09 23:21:29,842 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource 2

   結合測試方法中insertRoleList方法兩次調用insertRole方法,分析有關事務操做的流程:

  1.因爲insertRoleList方法的隔離級別爲讀/寫提交,傳播行爲爲REQUIRED,而初始時沒有當前事務,所以要首先建立insertRoleList方法的事務:

 DEBUG 2018-10-09 23:21:29,550 org.springframework.transaction.support.AbstractPlatformTransactionManager: 
  Creating new transaction with name [com.ssm.chapter13.service.impl.RoleListServiceImpl.insertRoleList]: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''

  而後DataSourceTransactionManager獲取JDBC鏈接事務,而且調整JDBC鏈接事務傳播級別爲級別2,對應MANDATORY,即方法必須在事務內運行。

 DEBUG 2018-10-09 23:21:29,763 org.springframework.jdbc.datasource.DataSourceTransactionManager: 
  Acquired Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] for JDBC transaction DEBUG 2018-10-09 23:21:29,766 org.springframework.jdbc.datasource.DataSourceUtils:
  Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 2 DEBUG 2018-10-09 23:21:29,767 org.springframework.jdbc.datasource.DataSourceTransactionManager:
  Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to manual commit

  2.在insertRoleList方法中,兩次調用了RoleServiceImpl類的insertRole方法,而insertRole方法的事務定義爲讀/寫提交和REQUIRES_NEW,以第一次調用爲例,因爲insertRoleList方法的傳播行爲定義爲REQUIRED,所以須要暫時掛起insertRoleList事務,而後建立新的insertRole事務:

 DEBUG 2018-10-09 23:21:29,767 org.springframework.transaction.support.AbstractPlatformTransactionManager: 
  Suspending current transaction, creating new transaction with name [com.ssm.chapter13.service.impl.RoleServiceImpl.insertRole]

  同理,獲取JDBC鏈接事務,而後設置傳播級別爲2,調整爲手動提交

 DEBUG 2018-10-09 23:21:29,782 org.springframework.jdbc.datasource.DataSourceTransactionManager: 
  Acquired Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] for JDBC transaction DEBUG 2018-10-09 23:21:29,782 org.springframework.jdbc.datasource.DataSourceUtils:
  Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 2 DEBUG 2018-10-09 23:21:29,783 org.springframework.jdbc.datasource.DataSourceTransactionManager:
  Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to manual commit

  3.進入SQL執行過程:

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6]
JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] will be managed by Spring
 ==>  Preparing: insert into t_role (role_name, note) values(?, ?) 
 ==> Parameters: role_name_1(String), note_1(String)
 <==    Updates: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3214ee6]

  4.若是沒有異常,則進行事務提交,而後進行JDBC事務提交,而後重置JDBC事務級別爲默認的4,即NOT_SUPPORTED即不支持事務,也就是不在事務中也能夠運行。而後釋放JDBC數據庫鏈接,而後將數據庫鏈接返還到數據庫鏈接池,而後恢復以前掛起的insertRoleList事務,即將進行第二次調用insertRole方法。

Initiating transaction commit
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver]
Resetting isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 4
Releasing JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] after transaction
Returning JDBC Connection to DataSource
Resuming suspended transaction after completion of inner transaction

  5.第二次調用insertRole時,重複2-4便可。

  

  8、Spring數據庫事務的一些問題

  1.@Transactional的自調用失效問題

  註解@Transactional的底層實現是Spring AOP技術,而Spring AOP技術使用的是動態代理技術。

  這就意味着對於靜態(static)和非public方法,註解@Transactional是失效的。並且,自調用也是使用過程當中容易犯的錯誤。

  自調用就是,一個類的一個方法去調用自身另一個方法的過程。

  修改RoleService接口,增長insertRoleList方法:

package com.ssm.chapter13.service;

import java.util.List;

import com.ssm.chapter13.pojo.Role;

public interface RoleService {
    
    public int insertRole(Role role);
    
    public int insertRoleList(List<Role> roleList);
    
}

  而後在實現類中實現這個方法,其中,insertRoleList方法中調用的是同一個類中的insertRole方法,在兩個方法上保持原來的事務設置。

package com.ssm.chapter13.service.impl;

@Service
public class RoleServiceImpl implements RoleService {
    
    @Autowired
    private RoleMapper roleMapper = null;
    
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public int insertRole(Role role) {
        return roleMapper.insertRole(role);
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
    public int insertRoleList(List<Role> roleList) {
        int count = 0;
        for (Role role : roleList) {
            try {
                // 調用自身類的insertRole方法,產生自調用問題
                insertRole(role);
                count++;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return count;
    }
}

  修改測試Main方法並運行:

package com.ssm.chapter13.main;
public class Chapter13Main {
    public static void main(String [] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext ("spring-cfg.xml");
//        RoleListService roleListService = ctx.getBean(RoleListService. class);
        RoleService roleService = ctx.getBean(RoleService.class);
        List<Role> roleList = new ArrayList<Role>();
        for (int i=1; i<=2; i++) {
            Role role = new Role();
            role.setRoleName("role_name_" + i);
            role.setNote("note_" + i);
            roleList.add(role);
        }
        int count = roleService.insertRoleList(roleList);
       System.out.println(count);
    }
}

  結果分析:從下面的結果能夠看出,兩次插入都只建立了一個SqlSession,也就是說,兩次插入都使用了同一事務,即在insertRole方法上進行@Transactional標註失效了。

 ...
 DEBUG 2018-10-10 21:22:58,512 org.springframework.transaction.support.AbstractPlatformTransactionManager: Creating new transaction with name [com.ssm.chapter13.service.impl.RoleServiceImpl.insertRoleList]: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''
 DEBUG 2018-10-10 21:22:58,750 org.springframework.jdbc.datasource.DataSourceTransactionManager: Acquired Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] for JDBC transaction
 DEBUG 2018-10-10 21:22:58,754 org.springframework.jdbc.datasource.DataSourceUtils: Changing isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 2
 DEBUG 2018-10-10 21:22:58,755 org.springframework.jdbc.datasource.DataSourceTransactionManager: Switching JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to manual commit
 DEBUG 2018-10-10 21:22:58,759 org.mybatis.spring.SqlSessionUtils: Creating a new SqlSession
DEBUG
2018-10-10 21:22:58,763 org.mybatis.spring.SqlSessionUtils: Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e3b3b2f] DEBUG 2018-10-10 21:22:58,772 org.mybatis.spring.transaction.SpringManagedTransaction: JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] will be managed by Spring DEBUG 2018-10-10 21:22:58,775 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: insert into t_role (role_name, note) values(?, ?) DEBUG 2018-10-10 21:22:58,794 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_1(String), note_1(String) DEBUG 2018-10-10 21:22:58,797 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1 DEBUG 2018-10-10 21:22:58,797 org.mybatis.spring.SqlSessionUtils: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e3b3b2f] DEBUG 2018-10-10 21:22:58,797 org.mybatis.spring.SqlSessionUtils: Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e3b3b2f] from current transaction DEBUG 2018-10-10 21:22:58,797 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: insert into t_role (role_name, note) values(?, ?) DEBUG 2018-10-10 21:22:58,798 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role_name_2(String), note_2(String) DEBUG 2018-10-10 21:22:58,798 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1
DEBUG 2018-10-10 21:22:58,798 org.mybatis.spring.SqlSessionUtils: Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e3b3b2f] DEBUG 2018-10-10 21:22:58,799 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e3b3b2f] DEBUG 2018-10-10 21:22:58,799 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e3b3b2f] DEBUG 2018-10-10 21:22:58,799 org.mybatis.spring.SqlSessionUtils$SqlSessionSynchronization: Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e3b3b2f] DEBUG 2018-10-10 21:22:58,799 org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction commit DEBUG 2018-10-10 21:22:58,799 org.springframework.jdbc.datasource.DataSourceTransactionManager: Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] DEBUG 2018-10-10 21:22:58,830 org.springframework.jdbc.datasource.DataSourceUtils: Resetting isolation level of JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] to 4 DEBUG 2018-10-10 21:22:58,831 org.springframework.jdbc.datasource.DataSourceTransactionManager: Releasing JDBC Connection [jdbc:mysql://localhost:3306/chapter6?useSSL=false, UserName=root@, MySQL-AB JDBC Driver] after transaction DEBUG 2018-10-10 21:22:58,831 org.springframework.jdbc.datasource.DataSourceUtils: Returning JDBC Connection to DataSource 2

  自調用引發@Transactional失效的根本緣由在於AOP的實現原理。因爲@Transactional的實現原理是AOP,而AOP的實現原理是動態代理。若是同一個類中的不一樣方法之間相互調用,那麼就不存在代理對象的調用,這樣就不會產生AOP去爲咱們設置@Transactional配置的參數了,這樣就出現了自調用註解失效的問題。

  爲了克服這個問題,第一種方法是像以前同樣把兩個方法分別位於兩個不一樣的類中,這樣Spring IoC容器中自動生成了RoleService的代理對象,這樣就可使用AOP;第二種方法是能夠直接從容器中獲取RoleService的代理對象,能夠改寫insertRoleList方法,從IoC容器中獲取RoleService的代理對象。

  此時,須要將代碼修改爲下面的內容,因爲須要經過應用上下文ctx的getBean方法獲取到Bean,所以類須要實現ApplicationContextAware方法而且增長ctx字段。

package com.ssm.chapter13.service.impl;

@Service
public class RoleServiceImpl implements RoleService, ApplicationContextAware {
    
    @Autowired
    private RoleMapper roleMapper = null;
    
    private ApplicationContext ctx = null;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public int insertRole(Role role) {
        return roleMapper.insertRole(role);
    }
    // 直接從Spring IoC容器中獲取到RoleService的代理對象
    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation= Isolation.READ_COMMITTED)
    public int insertRoleList2(List<Role> roleList) {
        int count = 0;
        // 從IoC容器中獲取了RoleService的Bean,也就是一個代理對象
        RoleService service = ctx.getBean(RoleService.class);
        for (Role role : roleList) {
            try {
                service.insertRole(role);
                count++;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return count;
    }

    // 增長setApplicationContext方法獲取ctx
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ctx = applicationContext;
    }
}

  2.錯誤使用Service

  假如想要在Controller中同時插入兩個角色,且必須在同一個事務中處理,其中insertRole方法是帶有@Transactional標註的方法

  當一個Controller使用Service方法時,若是這個Service標註有@Transactional,那麼它就會啓動一個事務,而一個Service方法完成後,它就會釋放該事務,因此先後兩個insertRole的方法是在兩個不一樣的事務中完成的。若是第一個插入成功了,而第二個插入失敗了,就會是數據庫不徹底同時成功或者失敗,可能產生嚴重的數據不一致的問題,給生產帶來嚴重的損失。

package com.test.errorUseService

@Controller
public class RoleController {

    @Autowired 
    private RoleService roleService = null;

    public void errorUseServices() {
        Role role1 = new Role();
        role1.setRoleName("role_name_1");
        role1.setNote("role_note_1");
        roleService.insertRole(role1);

        Role role2 = new Role();
        role2.setRoleName("role_name_2");
        role2.setNote("role_note_2");
        roleService.insertRole(role2);
    }
}

  3.過長時間佔用事務

  在企業的生產系統中,數據庫事務資源是最寶貴的資源之一,使用了數據庫事務只有,要及時釋放數據庫事務。

  假設在插入角色以後還須要操做一個文件,而操做文件的方法是一個與數據庫事務無關的操做:

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public int insertRole(Role role) {
int result = roleMapper.insertRole(role);
doSomethingWithoutTranaction();
return return; }

  因爲在insertRole方法上進行了@Transactional標註,所以當insertRole方法結束後Spring纔會釋放數據庫事務資源,也就是說,必須等到doSomethingWithoutTranaction方法執行完成以後才能夠釋放數據庫事務資源。若是doSomethingWithoutTranaction方法所消耗的時間特別長,那麼致使數據庫事務將長期得不到釋放,若是此時發生高併發的需求,會形成大量的併發請求得不到數據庫的事務資源而致使系統宕機。所以應該調整doSomethingWithoutTranaction方法的位置,使其放置在insertRole方法以外。

  4.錯誤捕捉異常

  Spring的事務中已經存在針對於異常的捕捉,即只要出現異常就會回滾事務。

  可是,當前須要將產品減庫存和保存交易在同一個事務裏面,要麼同時成功,要麼同時失敗。假設減庫存和保存交易的傳播行爲都爲REQUIRED,那麼下面的代碼會出現:Spring在整個數據庫事務所約定的流程中再也得不到任何的異常信息了。加入當庫存減小成功了,可是保存交易信息是卻出現了異常,此時因爲catch語句的緣由,Spring因爲得不到保存交易信息這個過程的異常,這個時候就會出現庫存減小,可是沒有交易信息的狀況。

@Autowired
private ProductService productService;

@Autowired
private TransactionService transactionService;

@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITED)
public int doTransaction(TransactionBean trans) {
    int result =0;
    try {
        // 執行減小庫存操做
        int result = productService.decreseStock(trans.getProductId(), trans.getQuantity());
        // 若是減小庫存成功,則保存記錄
        if(result > 0)  transactionService.save(trans);
    } catch(Exception ex) {
        // 自行捕獲異常而且處理異常
        // 記錄異常日誌
        log.info(ex);
    }
    return result;
}
      

  解決辦法是,捕獲到異常後,再自行拋出異常交由上級處理,讓Spring事務管理流程捕獲到異常,而後進行正確的事務管理。

@Autowired
private ProductService productService;

@Autowired
private TransactionService transactionService;

@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITED)
public int doTransaction(TransactionBean trans) {
    int result =0;
    try {
        // 執行減小庫存操做
        int result = productService.decreseStock(trans.getProductId(), trans.getQuantity());
        // 若是減小庫存成功,則保存記錄
        if(result > 0)  transactionService.save(trans);
    } catch(Exception ex) {
        // 自行捕獲異常而且處理異常
        // 記錄異常日誌
        log.info(ex);
        throw new RuntimeException(ex);
    }
    return result;
}
相關文章
相關標籤/搜索