轉(zhuǎn)載請注明來源 賴賴的博客
導(dǎo)語
只要你肯再堅持一會兒衣式,就會發(fā)現(xiàn)世界如此簡單谁不。
事務(wù)管理在持久層(數(shù)據(jù)庫)操作中是不可或缺的诅妹,其主要目的是為了保證操作的完整性票髓。
而Spring提供了強大的聲明式事務(wù)管理的方式疼进,其實現(xiàn)原理是采用了AOP對方法進行增強薪缆,使用起來非常方便,再也不用自己去管理事務(wù)(TRANSACTION)
實例
項目工程目錄結(jié)構(gòu)和代碼獲取地址
獲取地址(版本Log將會注明每一個版本對應(yīng)的課程)
https://github.com/laiyijie/SpringLearning
目錄結(jié)構(gòu)&數(shù)據(jù)庫
工程目錄結(jié)構(gòu)
數(shù)據(jù)庫
創(chuàng)建語句:
CREATE TABLE account (
username varchar(45) NOT NULL,
password varchar(45) NOT NULL,
name varchar(45) NOT NULL,
create_time bigint(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE account
ADD PRIMARY KEY (username);
CREATE TABLE `pocket` (
`username` varchar(45) NOT NULL,
`current_money` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `pocket`
ADD PRIMARY KEY (`username`);
運行工程
運行方式
- 右鍵App.java
- Run as
- Java Application
運行結(jié)果
Exception in thread "main" java.lang.RuntimeException: lailai
數(shù)據(jù)庫新增兩條記錄:
- account表中新增一條
- pocket表中新增一條
項目詳解
App.java
package me.laiyijie.demo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import me.laiyijie.demo.service.UserService;
public class App {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("root-context.xml");
UserService service = context.getBean(UserService.class);
service.createAccount("laiyijie", "123123", "lai");
service.createAccount("lailai", "123123", "lai");
context.close();
}
}
有兩條createAccount
語句伞广,卻只產(chǎn)生了一個賬號拣帽!問題肯定出現(xiàn)在createAccount
方法中
我們來具體看下UserService
的實現(xiàn)類
UserServiceImpl.java
package me.laiyijie.demo.service;
import java.sql.Connection;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private BasicDataSource dataSource;
@Transactional
public void createAccount(String username, String password, String name) throws Exception {
Connection connection = DataSourceUtils.getConnection(dataSource);
connection.createStatement().execute("INSERT INTO account (username, password, name,create_time) VALUES('"
+ username + "','" + password + "','" + name + "','" + System.currentTimeMillis() + "')");
if (username == "lailai") {
throw new RuntimeException("lailai");
}
connection.createStatement()
.execute("INSERT INTO pocket (username,current_money) VALUES('" + username + "','" + 0 + "')");
}
}
函數(shù)執(zhí)行過程:
- 通過
DataSourceUtils.getConnection
從dataSource
中獲取connection
Connection connection = DataSourceUtils.getConnection(dataSource);
- 執(zhí)行插入
account
表的insert語句
connection.createStatement().execute("INSERT INTO account (username, password, name,create_time) VALUES('" + username + "','" + password + "','" + name + "','" + System.currentTimeMillis() + "')");
- 判斷是否用戶名為
lailai
,是的話拋出運行時錯誤
if (username == "lailai") {
throw new RuntimeException("lailai");
}
- 執(zhí)行插入
pocket
表的insert語句
connection.createStatement().execute("INSERT INTO pocket (username,current_money) VALUES('" + username + "','" + 0 + "')");
那么很明顯嚼锄,在main
函數(shù)中執(zhí)行第二次createAccount
的過程中减拭,拋出了錯誤,而拋出錯誤的時間是在執(zhí)行完插入account表之后区丑!
理論上講拧粪,在拋錯之前的所有語句都應(yīng)該被執(zhí)行,也就是說沧侥,我們應(yīng)該向數(shù)據(jù)庫中插入了三行數(shù)據(jù)可霎,兩條account
數(shù)據(jù),一條pocket
數(shù)據(jù)宴杀,然而卻只有一條account
數(shù)據(jù)和一條pocket
數(shù)據(jù)癣朗。
讓我們看看lailai
這條account
數(shù)據(jù)去哪兒了。
@Transactional 注解(spring的事務(wù)管理)
細心的讀者可以發(fā)現(xiàn)旺罢,在createAccount
函數(shù)之前我們增加了一個注解@Transactional
旷余,這個是Spring事務(wù)管理的聲明,其寓意為:
當(dāng)執(zhí)行
createAccount
的時候扁达,將創(chuàng)建一個事務(wù)正卧,函數(shù)執(zhí)行成功,則正常提交跪解,當(dāng)函數(shù)拋出RuntimeExeption
的時候則回滾
這里有幾個隱含的條件非常重要:
- 數(shù)據(jù)庫連接的獲取一定要通過
DataSourceUtils.getConnection
來獲取炉旷。每一個事務(wù)都是綁定在一個連接上的,如果還是通過datasource.getConnection
方法獲取連接則有可能是獲取到別的連接(由于連接池的存在)而DataSourceUtils.getConnection
獲取的連接是當(dāng)前事務(wù)的連接 - 只有將異常拋出才能產(chǎn)生回滾,如果使用
try catch
處理了異常是不能回滾的 - 默認情況下只有
RuntimeException
的拋出會產(chǎn)生回滾窘行,如果需要自行定義可以回滾的異常骏啰,可以通過增加RollbackFor
屬性:@Transactional(rollbackFor={Exception.class})
- 如果使用mysql數(shù)據(jù)庫,請不要用 MyISAM引擎抽高,因為不支持事務(wù),請使用InnoDB引擎
說了這么多限制透绩,那么到底應(yīng)該如何在Spring中配置事務(wù)呢:
root-context.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" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://127.0.0.1:3306/myspring"
p:username="test" p:password="o34rWayJPPHgudtL" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="mysqlDataSource" />
<tx:annotation-driven transaction-manager="transactionManager" />
<context:component-scan base-package="me.laiyijie.demo"></context:component-scan>
</beans>
增加了兩個配置:
-
配置TransactionManager
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="mysqlDataSource" />
-
開啟注解驅(qū)動
<tx:annotation-driven transaction-manager="transactionManager" />
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.laiyijie</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
</dependencies>
</project>
無需增加額外依賴翘骂! spring-context中自帶!