手寫 Spring 框架準備工做之 Spring 核心事務管理

事務

事務的特性

  • 原子性(atomicity)
    一個事務必須被視爲一個不可分割的最小工做單元, 整個事務中的全部操做要麼所有執行成功, 要麼所有失敗回滾, 對於一個事務來講, 不可能只執行其中的一部分操做.
  • 一致性(consistency)
    事務必須使數據庫從一個一致性的狀態變換到另外一個一致性的狀態, 也就是說一個事務執行以前和執行以後都必須處於一致性的狀態. 拿轉帳來講, 假設用戶A和用戶B二者的錢加起來一共是5000, 那麼無論A和B之間如何轉帳, 轉幾回帳, 事務結束後兩個用戶的錢相加起來應該還得是5000, 這就是事務的一致性.
  • 隔離性(isolation)
    併發的事務之間是相互隔離的, 一個事務所作的修改在最終提交以前, 對其餘事務是不可見的. 例如, 帳戶A有5000元存款, 執行完轉帳語句(-500), 只要該事務沒有提交, 對其餘事務來講帳戶餘額仍是5000元.
  • 持久性(durability)
    事務一旦提交, 其對數據庫的修改就是永久性的, 即便系統崩潰, 修改的數據也不會丟失.

併發事務帶來的問題

  • 更新丟失(Lost Update)
    多個事務修改同一行記錄(都未提交), 後面的修改覆蓋了前面的修改.
  • 髒讀(Dirty Reads)
    一個事務能夠讀取另外一個事務未提交的數據.
  • 不可重複讀(Non-Repeatable Reads)
    同一個事務中執行兩次相同的查詢, 可能獲得不同的結果. 這是由於在查詢間隔內,另外一個事務修改了該記錄並提交了事務.
  • 幻讀(Phantom Reads)
    當某個事務在讀取某個範圍內的記錄時, 另外一個事務又在該範圍內插入了新的記錄, 當以前的事務再次讀取該範圍的記錄時, 會產生幻行.

隔離級別

在MySQL經常使用的存儲引擎中, 只有InnoDB支持事務, 因此這裏說的隔離級別指的是InnoDB下的事務隔離級別.java

  • READ UNCOMMITTED(讀未提交)
    在該隔離級別, 事務中的修改即便沒有提交, 對其餘事務也都是可見的. 避免了更新丟失的發生.
  • READ COMMITTED(讀已提交)
    在該隔離級別, 一個事務只能看見已經提交的事務所作的修改. 避免了更新丟失和髒讀.
  • REPEATABLE READ(可重複讀)
    MySQL默認的隔離級別, 該級別保證了在同一個事務中屢次讀取一樣的記錄的結果是一致的. 避免了更新丟失、髒讀、不可重複讀和幻讀. (注意看MySQL官網, RR隔離級別下解決了幻讀問題)
  • SERIALIZABLE(可串行化)
    SERIALIZABLE是最高的隔離級別, 它經過強制事務串行化執行, 避免了併發事務帶來的問題.
隔離級別 讀數據一致性 更新丟失 髒讀 不可重複讀 幻讀
讀未提交 最低級別, 只能保證不讀取物理上損壞的數據 ×
讀已提交 語句級 × ×
可重複讀 事務級 × × × ×
可串行化 最高級別, 事務級 × × × ×

Spring事務管理

下面是Spring事務註解的源代碼, 從中能夠看到Spring事務管理的四個屬性: Propagation, Isolation, timeout, readOnly.mysql

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}
複製代碼

事務傳播行爲spring

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}
複製代碼

事務傳播行爲是指方法之間事務的傳播. 好比, 在方法A中調用了方法B:sql

  • REQUIRED
    若是A有事務就使用當前事務, 若是A沒有事務, 就建立一個新事務.
  • SUPPORTS
    若是A有事務就使用當前事務, 若是A沒有事務, 就以非事務執行.
  • MANDATORY
    若是A有事務就使用當前事務, 若是A沒有事務, 就拋異常.
  • REQUIRES_NEW
    無論A有沒有事務都建立一個新事務.
  • NOT_SUPPORTED
    無論A有沒有事務都以非事務執行.
  • NEVER
    若是A有事務就拋異常, 若是A沒有事務, 就以非事務執行.
  • NESTED
    若是A沒有事務就建立一個事務, 若是A有事務, 就在當前事務中嵌套其餘事務. 事務隔離級別
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}
複製代碼

其中 DEFAULT 表示使用數據庫的隔離級別.數據庫

事務超時

爲了解決事務執行時間太長, 消耗太多資源的問題, 咱們能夠給事務設置一個超時時間, 若是事務執行時間超過了超時時間, 就回滾事務.編程

只讀事務

一些不須要事務的方法, 好比讀取數據, 就能夠設置爲只讀事務, 這樣能夠有效地提升一些性能.後端

手動管理事務

Spring使用 TransactionTemplate 事務模板來管理事務.bash

(1)dao層架構

public interface AccountDao {
	 //匯款
	public void out(String outer , Integer money);
	
	//收款
	public void in(String inner , Integer money);
}

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

	public void out(String outer, Integer money) {
		this.getJdbcTemplate().update("update account set money = money - ? where username = ?", money,outer);
	}

	public void in(String inner, Integer money) {
		this.getJdbcTemplate().update("update account set money = money + ? where username = ?", money,inner);
	}
}
複製代碼

(2)Service層併發

public interface AccountService {
	//轉帳
	public void transfer(String outer ,String inner ,Integer money);
}

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

	private TransactionTemplate transactionTemplate;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }	
    
    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    public void transfer(final String outer, final String inner, final Integer money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                accountDao.out(outer, money);
                //模擬故障
                int i = 1/0;
                accountDao.in(inner, money);
            }
        });
    }
}
複製代碼

(3)Spring配置

<beans>
    <!-- 一、datasource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!-- 二、dao  -->
    <bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 三、service -->
    <bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="transactionTemplate" ref="transactionTemplate"></property>
    </bean>

    <!-- 四、建立事務模板 -->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="txManager"></property>
    </bean>

    <!-- 五、配置事務管理器 ,管理器須要事務,事務從Connection得到,鏈接從鏈接池DataSource得到 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>
複製代碼

(4)測試代碼

@Test
public void demo(){
	String xmlPath = "applicationContext.xml";
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
	AccountService accountService =  (AccountService) applicationContext.getBean("accountService");
	accountService.transfer("jack", "rose", 1000);
}
複製代碼

Spring事務代理

(1)Service層

public interface AccountService {
	//轉帳
	public void transfer(String outer ,String inner ,Integer money);
}

public class AccountServiceImpl implements AccountService {

	private AccountDao accountDao;

	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}
	
	public void transfer(String outer, String inner, Integer money) {
		accountDao.out(outer, money);
		//模擬故障
		int i = 1/0;
		accountDao.in(inner, money);
	}
}
複製代碼

(2)Spring配置文件

<beans>
	<!-- 一、datasource -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
		<property name="user" value="root"></property>
		<property name="password" value="123"></property>
	</bean>
	
	<!-- 二、dao  -->
	<bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 三、service -->
	<bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
		<property name="accountDao" ref="accountDao"></property>
	</bean>
	
	<!-- 四、service 代理對象 
		4.1 proxyInterfaces 接口 
		4.2 target 目標類
		4.3 transactionManager 事務管理器
		4.4 transactionAttributes 事務屬性(事務詳情)
			prop.key :肯定哪些方法使用當前事務配置
			prop.text:用於配置事務詳情
				格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception
					傳播行爲		隔離級別		是否只讀		異常回滾		異常提交
				例如:
					<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> 默認傳播行爲,和隔離級別
					<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly</prop> 只讀
					<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+java.lang.ArithmeticException</prop>  有異常扔提交
	-->
	<bean id="proxyAccountService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
		<property name="proxyInterfaces" value="org.tx.service.AccountService"></property>
		<property name="target" ref="accountService"></property>
		<property name="transactionManager" ref="txManager"></property>
		<property name="transactionAttributes">
			<props>
				<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
			</props>
		</property>
	</bean>
	
	<!-- 五、配置事務管理器  -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
</beans>
複製代碼

(3)測試代碼

@Test
public void demo(){
	String xmlPath = "applicationContext.xml";
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
	AccountService accountService =  (AccountService) applicationContext.getBean("proxyAccountService");
	accountService.transfer("jack", "rose", 1000);
}
複製代碼

Spring + AspectJ

基於xml配置

(1)Service層

public interface AccountService {
	//轉帳
	public void transfer(String outer ,String inner ,Integer money);
}

public class AccountServiceImpl implements AccountService {

	private AccountDao accountDao;
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}
	
	public void transfer(String outer, String inner, Integer money) {
		accountDao.out(outer, money);
		//模擬故障
		int i = 1/0;
		accountDao.in(inner, money);
	}
}
複製代碼

(2)Spring配置文件

<beans>
	<!-- 一、datasource -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
		<property name="user" value="root"></property>
		<property name="password" value="123"></property>
	</bean>
	
	<!-- 二、dao  -->
	<bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 三、service -->
	<bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
		<property name="accountDao" ref="accountDao"></property>
	</bean>
	
	<!-- 四、事務管理器 -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 五、事務通知 
		<tx:attributes> 用於配置事務詳情(事務屬性)	
	-->
	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- 六、AOP編程 -->
	<aop:config>
		<aop:advisor advice-ref="txAdvice" pointcut="execution(* org.tx.service.*.*(..))"/>
	</aop:config>
</beans>
複製代碼

(3)測試代碼

@Test
public void demo(){
	String xmlPath = "applicationContext.xml";
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
	AccountService accountService =  (AccountService) applicationContext.getBean("accountService");
	accountService.transfer("jack", "rose", 1000);
}
複製代碼

基於註解

(1)Service層

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public class AccountServiceImpl implements AccountService {

	private AccountDao accountDao;
	public void setAccountDao(AccountDao accountDao) {
		this.accountDao = accountDao;
	}
	
	//或者 @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
	public void transfer(String outer, String inner, Integer money) {
		accountDao.out(outer, money);
		//模擬故障
		int i = 1/0;
		accountDao.in(inner, money);
	}
}
複製代碼

(2)Spring配置文件

<beans>
		<!-- 一、datasource -->
		<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
			<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
			<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
			<property name="user" value="root"></property>
			<property name="password" value="123"></property>
		</bean>
		
		<!-- 二、dao  -->
		<bean id="accountDao" class="org.tx.dao.impl.AccountDaoImpl">
			<property name="dataSource" ref="dataSource"></property>
		</bean>
		
		<!-- 三、service -->
		<bean id="accountService" class="org.tx.service.impl.AccountServiceImpl">
			<property name="accountDao" ref="accountDao"></property>
		</bean>
		
		<!-- 四、事務管理器 -->
		<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
			<property name="dataSource" ref="dataSource"></property>
		</bean>
		
		<!-- 五、將事務管理器交予Spring -->
		<tx:annotation-driven transaction-manager="txManager"/>
</beans>
複製代碼

(3)測試代碼

@Test
public void demo(){
	String xmlPath = "applicationContext.xml";
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
	AccountService accountService =  (AccountService) applicationContext.getBean("accountService");
	accountService.transfer("jack", "rose", 1000);
}
複製代碼

來源: blog.csdn.net/litianxiang…

相關文章

開源項目推薦

做者的開源項目推薦:

  • Pre 基於 Spring Boot 、Spring Security 、Vue 的先後端分離的的 RBAC 權限管理系統
  • Prex 基於 Spring Cloud、Oauth2 、Vue 的先後端分離的微服務架構 RBAC 權限管理系統

關注公衆號回覆開源項目便可獲取

相關文章
相關標籤/搜索