一、概述
1.1 什麼是事務
事務(Transaction),一般是指要做的或所做的事情。在計算機術語中是指訪問並可能更新數據庫中各種數據項的一個程序執行單元(unit)
1.2 事務的特性
- 原子性(Atomicity)
一個事務中的所有操作不可分割,要麼全部成功,要麼全部失敗
- 一致性(Consistency)
一個事務執行完成前後,要保證事務前後業務邏輯的一致性
- 隔離性(Isolation)
事務的執行不能受其他事務干擾,彼此隔離
- 持久性(Durability)
事務的提交或者回滾都需要持久化到存儲介質中
1.3 spring事務
spring對事務提供了兩種實現方式:聲明式事務和編程式事務管理;spring事務基於AOP實現
1.4 spring事務的隔離級別
- 併發事務產生的問題
問題 | 描述 | 備註 |
---|---|---|
髒讀 | 一個事務對數據進行更新操作,另一個事務對數據進行讀取,前者進行了回滾,後者讀取到的數據便是髒數據,稱之爲髒讀 | |
不可重複讀 | 一個事務對數據進行多次重複讀取,另一個事務在讀取期間對數據進行了更新操作,前者讀取到的數據前後不一致,稱之爲不可重複讀 | |
幻讀 | 一個事務對數據進行多次讀取操作,期間另一個事務進行了插入數據的操作,前者讀取數據前後不一致,稱之爲幻讀 |
- 事務的隔離級別及解決的問題
隔離級別 | 含義 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|---|
READ UNCOMMITTED | 讀未提交 | |||
READ COMMITTED | 讀已提交 | ✅ | ||
REPEATABLE READ | 可重複讀 | ✅ | ✅ | |
SERIALIZABLE | 序列化 | ✅ | ✅ | ✅ |
1.5 spring事務的傳播特性
傳播行爲 | 含義 | 備註 |
---|---|---|
PROPAGATION_REQUIRED | 表示當前方法必須運行在事務中,如果當前沒有事務,就新建一個事務 | |
PROPAGATION_SUPPORTS | 如果當前有事務,就在事務中執行,如果當前沒有事務,就以非事務方式執行 | |
PROPAGATION_MANDATORY | 表示當前方法必須在事務中運行,如果當前沒有事務,就拋出異常 | |
PROPAGATION_REQUIRED_NEW | 表示當前方法必須運行在自己的事務中,如果當前存在事務,把當前事務掛起 | |
PROPAGATION_NOT_SUPPORTED | 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起 | |
PROPAGATION_NEVER | 以非事務方式執行,如果當前存在事務,則拋出異常 | |
PROPAGATION_NESTED | 嵌套事務 |
二、xml配置實現
2.1 步驟
- 配置事務管理器
- 配置通知
- 配置切入點表達式
- 織入通知
- 配置事務屬性
2.2 示例
beans.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置service -->
<bean id="userService" class="com.lizza.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<!-- 配置dao,由於dao繼承了JdbcDaoSupport,所以需要初始化dataSource -->
<bean id="userDao" class="com.lizza.dao.impl.UserDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置數據源,使用spring提供的dataSource:DriverManagerDataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- spring基於xml聲明式事務的配置 -->
<!--
1. 配置事務管理器
2. 配置通知
3. 配置切入點表達式
4. 織入通知
5. 配置事務屬性
-->
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 配置事務屬性
name: 方法名
isolation: 隔離級別, 默認爲DEFAULT, 爲數據庫的隔離級別
propagation: 事務的傳播行爲, 默認爲REQUIRED, 表示一定會有事務; 涉及數據編輯操作選擇REQUIRED, 涉及數據查詢操作選擇SUPPORTS
read-only: 是否只讀事務, 默認值爲false, 表示讀寫
timeout: 設置事務的超時時間, 默認值爲-1, 表示永不超時
rollback-for: 指定一個異常, 發生該異常時, 事務進行回滾, 其他異常不回滾; 默認值爲都需要回滾
no-rollback-for: 指定一個異常, 發生改一次, 事務不回滾, 其他異常回滾; 默認值爲都需要回滾
-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="transfer*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 配置切入點表達式 -->
<aop:pointcut id="txPoint" expression="execution(* com.lizza.service.impl.*.*(..))"/>
<!-- 織入通知 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint" ></aop:advisor>
</aop:config>
</beans>
三、註解配置實現
3.1 關鍵註解
@EnableTransactionManagement
:開啓spring事務管理@Transactional
:指定需要運行在事務中的類或者方法
3.2 步驟
- 配置JdbcConfig
@Configuration
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean("dataSource")
public DataSource createDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
- 配置TransactionConfig
@Configuration
public class TransactionConfig {
/**
* 配置事務管理器
* @author: [email protected]
* @date: 2020/4/6 10:54 下午
* @param dataSource
* @return org.springframework.transaction.PlatformTransactionManager
*/
@Bean("transactionManager")
public PlatformTransactionManager createPlatformTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
- 配置SpringConfig
@Configuration
@ComponentScan("com.lizza")
@Import({JdbcConfig.class, TransactionConfig.class})
@EnableTransactionManagement
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
}
- 業務層配置
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public int addOne(User user) {
return userDao.addOne(user);
}
@Override
public int deleteOne(int id) {
return userDao.deleteOne(id);
}
@Override
public int updateOne(User user) {
return userDao.updateOne(user);
}
@Override
public User findOne(int id) {
return userDao.findOne(id);
}
@Override
public List<User> findAll() {
return userDao.findAll();
}
@Override
public int transferMoney(int from_id, int to_id, double money) {
// 1. 獲取付款賬戶和收款賬戶
User from_user = userDao.findOne(from_id);
User to_user = userDao.findOne(to_id);
// 2. 付款賬戶付款,收款賬戶收款
from_user.setMoney(from_user.getMoney() - money);
to_user.setMoney(to_user.getMoney() + money);
// 3. 更新賬戶信息
userDao.updateOne(from_user);
int i = 100/0;
userDao.updateOne(to_user);
return 0;
}
}