想到哪說到哪
事務四個特性:
原子性
事務是數(shù)據(jù)庫的邏輯工作單位,事務中包括的諸操作要么全做,要么全不做。
一致性
事務執(zhí)行的結果必須是使數(shù)據(jù)庫從一個一致性狀態(tài)變到另一個一致性狀態(tài)。一致性與原子性是密切相關的。
隔離性
一個事務的執(zhí)行不能被其他事務干擾。
持續(xù)性
一個事務一旦提交,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就應該是永久性的。
在正常的web項目中,事務和DAO一直如影隨行,所以有人認為配置事務和DAO的關系是密不可分,不能分離的。其實不然,DAO可以完全脫離容器獨立存在。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd"> <!-- 打開springmvc自動注解 --> <mvc:annotation-driven /> <!-- 掃描的包 --> <context:component-scan base-package="com.zhu" /> <!-- 數(shù)據(jù)源默認將autoCommit設置為true --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:username="zhu" p:password="zhu" /> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /></beans>
zhu_test.sql
CREATE TABLE IF NOT EXISTS zhu_test (NAME VARCHAR(20),age INT(100)) ENGINE = INNODB;
測試類:
package com.zhu.service;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Service;import com.alibaba.druid.pool.DruidDataSource;@Service("service1")public class UserJdbcWithoutTransManagerService { @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName,int toAdd){ String sql = "UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?"; jdbcTemplate.update(sql,toAdd,userName); } public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("file:src/main/resources/applicationContext.xml"); UserJdbcWithoutTransManagerService service = (UserJdbcWithoutTransManagerService)ctx.getBean("service1"); JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate"); DruidDataSource druidDataSource = (DruidDataSource)jdbcTemplate.getDataSource(); //①.檢查數(shù)據(jù)源autoCommit的設置 System.out.println("autoCommit:"+ druidDataSource.isDefaultAutoCommit()); //②.插入一條記錄,初始分數(shù)為10 jdbcTemplate.execute( "INSERT INTO zhu_test VALUES('tom',10)"); //③.調用工作在無事務環(huán)境下的服務類方法,將分數(shù)添加20分 service.addScore("tom",20); //④.查看此時用戶的分數(shù) @SuppressWarnings("deprecation") int score = jdbcTemplate.queryForInt("SELECT age FROM zhu_test WHERE name ='tom'"); System.out.println("score:"+score); jdbcTemplate.execute("DELETE FROM zhu_test WHERE name='tom'"); }}
運行結果:
applicationContext.xml中并沒有配置事務,但是還是持久化到數(shù)據(jù)庫中去了。DataSource默認設置是自動提交的,也就是說,在執(zhí)行了CRUD之后,會馬上持久化到數(shù)據(jù)庫中。如果設置自動提交為false,那么在jdbcTemplate執(zhí)行完之后并不會馬上持久化到數(shù)據(jù)庫中,除非手動提交。
雖說沒有事務管理,程序依然可以進行數(shù)據(jù)的CRUD操作,但是沒有事務管理,在數(shù)據(jù)安全同步方面會面臨很大的挑戰(zhàn)。栗子太多,不一一舉例,看代碼
我們故意提交一條錯誤的sql語句
package com.zhu.service;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Service;import com.alibaba.druid.pool.DruidDataSource;@Service("service1")public class UserJdbcWithoutTransManagerService { @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName,int toAdd){ String sql = "UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?"; jdbcTemplate.update(sql,toAdd,userName); } public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("file:src/main/resources/applicationContext.xml"); UserJdbcWithoutTransManagerService service = (UserJdbcWithoutTransManagerService)ctx.getBean("service1"); JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate"); DruidDataSource druidDataSource = (DruidDataSource)jdbcTemplate.getDataSource(); //①.檢查數(shù)據(jù)源autoCommit的設置 System.err.println("autoCommit:"+ druidDataSource.isDefaultAutoCommit()); //②.插入一條記錄,初始分數(shù)為10 jdbcTemplate.execute( "INSERT INTO zhu_test VALUES('tom',10)"); //③.執(zhí)行一條錯誤sql語句 jdbcTemplate.execute( "INSERT INTO zhu_test VALUES('tom1',10,00)"); //④.調用工作在無事務環(huán)境下的服務類方法,將分數(shù)添加20分(不會執(zhí)行) service.addScore("tom",20); } }
清空數(shù)據(jù),然后執(zhí)行結果是:②成功插入數(shù)據(jù)tom 10 然后③錯誤的sql拋出異常 然后④更新操作也不會執(zhí)行。
正常的邏輯是在同一方法或者類中執(zhí)行一系列的CRUD操作,其中一條出出現(xiàn)問題會拋出異常,然后已經(jīng)執(zhí)行完的語句回滾到發(fā)生異常之前的狀態(tài)。
這種情況在正常的開發(fā)環(huán)境是最基本的常識性錯誤,開發(fā)過程中一定要避免。
然后是配置事務。
applicationContext.xml文件中添加事務和模型視圖的配置
package com.zhu.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class NoTMController { //②.自動注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; //③.通過Spring MVC注解映URL請求 @RequestMapping("/logon") @ResponseBody public String logon(String userName,String password){ String sql = "UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?"; if(isRightUser(userName,password)){ //執(zhí)行更新操作(年齡加20) jdbcTemplate.update(sql,20,"tom"); //執(zhí)行錯誤語句 jdbcTemplate.execute( "INSERT INTO zhu_test VALUES('tom1',10,00)"); return "success"; }else{ return "fail"; } } private boolean isRightUser(String userName,String password){ //do sth... return true; }}
啟動項目 輸入網(wǎng)址
影響了一條數(shù)據(jù),操作已經(jīng)完成
所謂事務傳播行為就是多個事務方法相互調用時,事務如何在這些方法間傳播。Spring 支持 7 種事務傳播行為:
Spring 默認的事務傳播行為是 PROPAGATION_REQUIRED,它適合于絕大多數(shù)的情況。假設 ServiveX#methodX() 都工作在事務環(huán)境下(即都被 Spring 事務增強了),假設程序中存在如下的調用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那么這 3 個服務類的 3 個方法通過 Spring 的事務傳播機制都工作在同一個事務中。
事務與線程
由于 Spring 的事務管理器是通過線程相關的 ThreadLocal 來保存數(shù)據(jù)訪問基礎設施,再結合 IOC 和 AOP 實現(xiàn)高級聲明式事務的功能,所以 Spring 的事務天然地和線程有著千絲萬縷的聯(lián)系。
我們知道 Web 容器本身就是多線程的,Web 容器為一個 Http 請求創(chuàng)建一個獨立的線程,所以由此請求所牽涉到的 Spring 容器中的 Bean 也是運行于多線程的環(huán)境下。在絕大多數(shù)情況下,Spring 的 Bean 都是單實例的(singleton),單實例 Bean 的最大的好處是線程無關性,不存在多線程并發(fā)訪問的問題,也即是線程安全的。
一個類能夠以單實例的方式運行的前提是“無狀態(tài)”:即一個類不能擁有狀態(tài)化的成員變量。我們知道,在傳統(tǒng)的編程中,DAO 必須執(zhí)有一個 Connection,而 Connection 即是狀態(tài)化的對象。所以傳統(tǒng)的 DAO 不能做成單實例的,每次要用時都必須 new 一個新的實例。傳統(tǒng)的 Service 由于將有狀態(tài)的 DAO 作為成員變量,所以傳統(tǒng)的 Service 本身也是有狀態(tài)的。
但是在 Spring 中,DAO 和 Service 都以單實例的方式存在。Spring 是通過 ThreadLocal 將有狀態(tài)的變量(如 Connection 等)本地線程化,達到另一個層面上的“線程無關”,從而實現(xiàn)線程安全。Spring 不遺余力地將狀態(tài)化的對象無狀態(tài)化,就是要達到單實例化 Bean 的目的。
由于 Spring 已經(jīng)通過 ThreadLocal 的設施將 Bean 無狀態(tài)化,所以 Spring 中單實例 Bean 對線程安全問題擁有了一種天生的免疫能力。不但單實例的 Service 可以成功運行于多線程環(huán)境中,Service 本身還可以自由地啟動獨立線程以執(zhí)行其它的 Service。
于是我們修改我們的代碼
package com.zhu.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class NoTMController { //②.自動注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; //③.通過Spring MVC注解映URL請求 @RequestMapping("/logon") @ResponseBody public String logon(String userName,String password){ System.err.println(userName); if(isRightUser(userName,password)){ Thread h1 = new Thread(){ String sql = "UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?"; @Override public void run() { jdbcTemplate.update(sql,20,"tom"); } }; h1.start(); jdbcTemplate.execute( "INSERT INTO zhu_test VALUES('tom1',10,00)"); return "success"; }else{ return "fail"; } } private boolean isRightUser(String userName,String password){ //do sth... return true; }}
代碼還是原來的代碼,只是將更新年齡的操作放到其他線程中,然后再進項上面的操作。
結果就是更新成功,插入失敗,拋出異常。
在相同線程中進行相互嵌套調用的事務方法工作于相同的事務中。如果這些相互嵌套調用的方法工作在不同的線程中,不同線程下的事務方法工作在獨立的事務中。