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; } }
從代碼能夠看出,即便是執行一條簡單的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; } }
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; }
4、MyBatis-Spring項目
目前大部分的互聯網項目中都使用SSM搭建平臺的。使用Spring IoC能夠有效管理各種Java資源,達到即插即拔的功能;經過AOP框架,數據庫事務能夠委託給Spring處理,消除很大一部分的事務代碼,配合MyBatis的高靈活、可配置、可優化SQL等特性,徹底能夠構建高性能的大型網站。
在Spring環境中使用MyBatis也更加簡單,節省了很多代碼,甚至能夠不用SqlSessionFactory、SqlSession等對象。由於MyBatis-Spring爲咱們封裝了它們。
配置MyBatis-Spring項目須要下面幾步:
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>
有三個屬性:
其中,若是同時配置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配置文件:包括dataSource、sqlSessionFactory和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特性
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>
分析:
dataSource、SqlSessionFactory和MapperScannerConfigurer用來支持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; }