JDBC事務管理
Spring提供編程式的事務管理(Programmatic transaction manage- ment)與聲明式的事務管理(Declarative transaction management),爲不同的事務實現提供了一致的編程模型,這節以JDBC事務爲例,介紹Spring的事務管理。
5.3.1 Spring對事務的支持
事務是一組原子(Atomic)操作的工作單元,以數據庫存取的實例來說,就是一組SQL指令,這一組SQL指令必須全部執行成功,若因爲某個原因未全部執行成功(例如其中一行SQL有錯誤),則先前所有執行過的SQL指令都會被撤消。
舉個簡單的例子,一個客戶從A銀行轉賬至B銀行,要作的動作爲從A銀行的賬戶扣款、在B銀行的賬戶加上轉賬的金額,兩個動作必須成功,如果有一個動作失敗,則此次轉賬失敗。
事務還必須保持所參與資源的一致性(Consistent),例如在銀行賬戶的例子中,兩個賬戶的轉賬金額,B賬戶取款的金額不能大於A賬戶的存款金額。 每個事務彼此之間必須是隔離的(Isolated),例如在A賬戶中可能有兩筆事務,同時進行存款與提款的動作,兩個事務基本上不需意識到彼此的存在。事 務還必須是可持續的(Durable),在某一筆事務之後,這筆事務必須是被記錄下來的。
在這裏將介紹JDBC如何使用事務管理。首先來看看事務的原子性實現,在JDBC中,可以操作Connection的setAutoCommit() 方法,給定false參數,在下達一連串的SQL語句後,自行執行Connection的commit()來送出變更,如果中間發生錯誤,則執行 rollback() 來撤消所有的執行,例如:
try
{
. . . . . connection . setAutoCommit ( false) ; . . . . . // 一連串SQL操作 connection . commit ( ) ; } catch ( SQLException ) { // 發生錯誤,撤消所有變更 connection . rollback ( ) ; } |
在Spring中對JDBC的事務管理加以封裝,Spring事務管理的抽象關鍵在於org.springframework.transaction.PlatformTransactionManager接口的實現:
public
interface
PlatformTransactionManager {
TransactionStatus getTransaction( TransactionDefinition definition) throws TransactionException; void commit ( TransactionStatus status ) throws TransactionException; void rollback ( TransactionStatus status ) throws TransactionException; } |
PlatformTransactionManager 接口有許多具體的事務實現類,例如DataSourceTransactionManager、 HibernateTransactionManager、JdoTransaction- Manager、JtaTransactionManager等,通過依賴於PlatformTransactionManager接口及各種的技術實 現,Spring在事務管理上可以讓開發人員使用一致的編程模型,即使所使用的是不同的事務管理技術。
TransactionException是Unchecked Exception.事務的失敗通常都是致命的錯誤,Spring不強迫您一定要處理,而是讓您自行選擇是否要捕捉異常。
getTransaction() 方法根據一個TransactionDefinition對象來回傳一個TransactionStatus對 象,TransactionDefinition接口的實例定義了事務的隔離程度(Isolation level)、傳播行爲(Propagation behavior)、超時(Timeout)、只讀(Read-only)等,TransactionStatus代表着一個新的事務發起或已經存在的事 務,您可以通過它來控制事務的執行或調查的狀態:
public
interface
TransactionStatus {
boolean isNewTransaction( ) ; void setRollbackOnly( ) ; boolean isRollbackOnly( ) ; } |
Spring提供編程式的事務管理(Programmatic transaction management)與聲明式的事務管理(Declarative transaction management):
l 編程式的事務管理
編程式的事務管理可以清楚地控制事務的邊界,也就是讓您自行實現事務開始時間、撤消操作的時機、結束時間等,可以實現細粒度的事務控制。
l 聲明式的事務管理
然而多數的情況下,事務並不需要細粒度的控制,而是採用聲明式的事務管理,好處是Spring事務管理的相關API可以不用介入程序之中,從對象的角度來 看,它並不知道自己正被納入事務管理之中,在不需要事務管理的時候,只要在設置文件上修改一下設置,即可移去事務管理服務。
5.3.2 JDBC編程事務管理Spring提供兩種方式實現編程式的事務管理,一是直接使用PlatformTransaction- Manager實現,二是使用org.springframework.transaction.support.Transaction- Template.
先來看看如何使用PlatformTransactionManager,在這裏使用它的實現類 DataSourceTransactionManager,可以改寫一下之前5.2.1節中的JdbcTemplateDemo項目,讓它具有事務管理 功能,修改一下UserDAO類的insert() 方法來作示範:ProgrammaticTransactionDemo UserDAO.java
package
onlyfun.
caterpillar;
import
java
.
util
.
Iterator
;
import
java
.
util
.
List
;
import
java
.
util
.
Map
;
import
javax
.
sql
.
DataSource
;
import
org
.
springframework.
dao.
DataAccessException;
import
org
.
springframework.
jdbc.
core.
JdbcTemplate;
import
org
.
springframework.
jdbc.
datasource
.
DataSourceTransactionManager;
import
org
.
springframework.
transaction
.
TransactionDefinition;
import
org
.
springframework.
transaction
.
TransactionStatus;
import
org
.
springframework.
transaction
.
support.
DefaultTransactionDefinition;
public
class
UserDAO implements
IUserDAO {
private
DataSourceTransactionManager transactionManager;
private
DefaultTransactionDefinition def;
private
JdbcTemplate jdbcTemplate;
public
void
setDataSource(
DataSource
dataSource
)
{
jdbcTemplate =
new
JdbcTemplate(
dataSource
)
;
transactionManager =
new
DataSourceTransactionManager(
dataSource
)
;
// 建立事務的定義
def =
new
DefaultTransactionDefinition(
)
;
def.
setPropagationBehavior(
TransactionDefinition.
PROPAGATION_REQUIRED)
;
}
public
void
insert
(
User user)
{
String
name
=
user.
getName
(
)
;
int
age =
user.
getAge(
)
.
intValue
(
)
;
TransactionStatus status
=
transactionManager.
getTransaction(
def)
;
try
{
jdbcTemplate.
update
(
"INSERT INTO user (name,age) "
+
"VALUES('"
+
name
+
"',"
+
age +
")"
)
;
// 下面的SQL有錯誤,用以測試
事務
jdbcTemplate.
update
(
"INSER INTO user (name,age) "
+
"VALUES('"
+
name
+
"',"
+
age +
")"
)
;
}
catch
(
DataAccessException e)
{
transactionManager.
rollback
(
status
)
;
throw
e;
}
transactionManager.
commit
(
status
)
;
}
public
User find
(
Integer
id
)
{
List
rows =
jdbcTemplate.
queryForList(
"SELECT * FROM user WHERE id="
+
id
.
intValue
(
)
)
;
Iterator
it =
rows.
iterator
(
)
;
if
(
it.
hasNext
(
)
)
{
Map
userMap =
(
Map
)
it.
next
(
)
;
Integer
i =
new
Integer
(
userMap.
get
(
"id"
)
.
toString
(
)
)
;
String
name
=
userMap.
get
(
"name"
)
.
toString
(
)
;
Integer
age =
new
Integer
(
userMap.
get
(
"age"
)
.
toString
(
)
)
;
User user =
new
User(
)
;
user.
setId
(
i)
;
user.
setName
(
name
)
;
user.
setAge(
age)
;
return
user;
}
return
null
;
}
}
在 insert()方法中使用了DataSourceTransactionManager來進行事務管理,如果發生了異常,則catch區塊中會進行事務 的Rollback,在insert() 方法中故意寫入錯誤的SQL(注意INSERT方法少寫了一個T),因此實際上數據並不會被儲存至數據庫中。
要使用MySQL數據庫進行事務處理,必須建立支持事務的表格類型,例如InnoDB的表格類型,這裏用來建立表格的SQL如下所示:
CREATE
TABLE
user
(
id INT( 11) NOT NULL auto_increment PRIMARY KEY , name VARCHAR ( 100) NOT NULL default '' , age INT ) TYPE = InnoDB; |
另一個實現編程式事務管理的方法是使用TransactionTemplate,它需要一個TransactionManager實例,如下所示:
TransactionTemplate transactionTemplate =
new TransactionTemplate( transactionManager) ; . . . transactionTemplate. execute ( new TransactionCallback( ) { public Object doInTransaction( TransactionStatus status ) { return jdbcTemplate. update ( "INSERT INTO user (name,age) " + "VALUES('" + name + "'," + age + ")" ) ; } } ) ; |
如果發生了異常,則會進行Rollback,否則提交事務,如果沒有回傳值,則也可以使用TransactionCallbackWithoutResult:
transactionTemplate.
execute
(
new TransactionCallbackWithoutResult( ) { public void doInTransactionWithoutResult( TransactionStatus status ) { . . . . } } ) ; |
5.3.3 JDBC聲明事務管理
Spring聲明式的事務管理依賴它的AOP框架來完成。使用聲明事務管理的好處是,事務管理不能侵入您所開發的組件,具體來說,DAO對象不會意識到正 在事務管理之中,事實上也應當如此,因爲事務管理是屬於系統層面的服務,而不是業務邏輯的一部分,如果想要改變事務管理策略的話,也只需要在定義文件中重 新配置。
舉個例子來說,可以將5.2.1節中的JdbcTemplateDemo 項目修改一下,在不修改UserDAO類的情況下,可以爲它加入事務管理的服務,一個簡單的方法是使用 TransactionProxyFactoryBean,指定要介入的事務管理對象及其方法,這需要在定義文件中修改,如下所示:
DeclarativeTransactionDemo beans-config.xml
<
?
xml version
=
"1.0"
encoding
=
"UTF-8"
?
>
< beans xmlns= "http://www.springframework.org/schema/beans" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" > < bean id= "dataSource" class= "org.springframework.jdbc. → datasource.DriverManagerDataSource" destroy-method= "close" > < property name= "driverClassName" value= "com.mysql.jdbc.Driver" / > < property name= "url" value= "jdbc:mysql://localhost:3306/demo" / > < property name= "username" value= "caterpillar" / > < property name= "password" value= "123456" / > < / bean> < bean id= "transactionManager" class= "org.springframework.jdbc. → datasource.DataSourceTransactionManager" > < property name= "dataSource" ref= "dataSource" / > < / bean> < bean id= "userDAO" class= "onlyfun.caterpillar.UserDAO" > < property name= "dataSource" ref= "dataSource" / > < / bean> < bean id= "userDAOProxy" class= "org.springframework.transaction. → interceptor.TransactionProxyFactoryBean" > < property name= "proxyInterfaces" > < list> < value> onlyfun. caterpillar. IUserDAO< / value> < / list> < / property> < property name= "target" ref= "userDAO" / > < property name= "transactionManager" ref= "transactionManager" / > < property name= "transactionAttributes" > < props> < prop key= "insert*" > PROPAGATION_REQUIRED< / prop> < / props> < / property> < / bean> < / beans> |
TransactionProxyFactoryBean需要一個TransactionManager,由於這裏使用的是JDBC,所以使用 DataSourceTransactionManager,TransactionProxyFactoryBean是個代理對象,"target" 屬性指定要代理的對象,事務管理會自動介入指定的方法前後,這裏使用 "transactionAttributes" 屬性指定,"insert*" 表示指定方法名稱以insert開頭的都要納入事務管理,您也可以指定方法全名,如果在方法執行過程中發生錯誤,則所有先前的操作自動撤回,否則正常提 交。
在"insert*" 等方法上指定了 "PROPAGATION_REQUIRED",表示在目前的事務中執行操作,如果事務不存在就建立一個新的,相關的常數意義都可以在API文件的 TransactionDefinition接口中找到。您可以加上多個事務定義,中間使用逗號 "," 區隔,例如可以加上只讀,或者是指定某個異常發生時撤回操作:
PROPAGATION_REQUIRED,readOnly,-MyCheckedException
MyCheckedException前面加上 "-" 時,表示發生指定異常時撤消操作,如果前面加上 "+",表示發生異常時立即提交。
由於"userDAO"被"userDAOProxy"代理了,所以要做的是取得"userDAOProxy",而不是"userDAO",例如:
DeclarativeTransactionDemo SpringDAODemo.java
package
onlyfun.
caterpillar;
import org . springframework. context . ApplicationContext; import org . springframework. context . support. ClassPathXmlApplicationContext; public class SpringDAODemo { public static void main( String [ ] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "beans-config.xml" ) ; User user = new User( ) ; user. setName ( "caterpillar" ) ; user. setAge( new Integer ( 30) ) ; IUserDAO userDAO = ( IUserDAO) context . getBean( "userDAOProxy" ) ; userDAO. insert ( user) ; user = userDAO. find ( new Integer ( 1) ) ; System . out. println ( "name: " + user. getName ( ) ) ; } } |
您也可以設置不同的TransactionInterceptor來得到更多的管理細節,例如:
<
?
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-2.0.xsd" > < bean id= "dataSource" class= "org.springframework.jdbc. → datasource.DriverManagerDataSource" destroy-method= "close" > < property name= "driverClassName" value= "com.mysql.jdbc.Driver" / > < property name= "url" value= "jdbc:mysql://localhost:3306/demo" / > < property name= "username" value= "caterpillar" / > < property name= "password" value= "123456" / > < / bean> < bean id= "transactionManager" class= "org.springframework.jdbc. → datasource.DataSourceTransactionManager" > < property name= "dataSource" ref= "dataSource" / > < / bean> < bean id= "userDAO" class= "onlyfun.caterpillar.UserDAO" > < property name= "dataSource" ref= "dataSource" / > < / bean> < bean id= "transactionInterceptor" class= "org.springframework.transaction. → interceptor.TransactionInterceptor" > < property name= "transactionManager" ref= "transactionManager" / > < property name= "transactionAttributeSource" value= "onlyfun.caterpillar.UserDAO.insert*= → PROPAGATION_REQUIRED " / > < / bean> < bean id= "userDAOProxy" class= "org.springframework.aop. → framework.ProxyFactoryBean" > < property name= "proxyInterfaces" > < list> < value> onlyfun. caterpillar. IUserDAO< / value> < / list> < / property> < property name= "target" ref= "userDAO" / > < property name= "interceptorNames" > < list> < value> transactionInterceptor< / value> < / list> < / property> < / bean> |
同樣的,由於不再於設置文件中設置代理對象,所以直接取得"userDAO"實例進行操作即可。