spring 中的事务管理

 事务的四个关键属性(ACID)

原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.

一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.

隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.

持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中

 

Spring 中的事务管理器

DataSourceTransactionManager在应用程序中只需要处理一个数据源, 而且通过 JDBC 存取

事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中

 

用 @Transactional 注解声管理事务

为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解

在 Bean 配置文件中只需要启用<tx:annotation-driven> 元素, 并为之指定事务管理器就可以了

 

事务传播属性

当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行,也可能开启一个新事务, 并在自己的事务中运行.

REQUIRED它默认会在现有的事务内运行

REQUIRED_NEW它表示该方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它.

 

并发事务所导致的问题可以分为下面三种类型:

脏读: 对于两个事物 T1, T2,T1  读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.

不可重复读:对于两个事物 T1,T2, T1  读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.

幻读:对于两个事物 T1, T2,T1  从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.

 

隔离事务属性可以指定事务的隔离级别

注解声明式地管理事务时可以在 @Transactional 的 isolation 属性中设置隔离级别

或可以在 <tx:method> 元素中指定隔离级别

 

回滚事务属性

事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类.

rollbackFor:  遇到时必须进行回滚

noRollbackFor: 一组异常类,遇到时必须不回滚

 

超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.

只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.

 

注解方式

SpringTransactionTest

package com.spring.tx;
 
import static org.junit.jupiter.api.Assertions.*;
 
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
class SpringTransactionTest {
 
       private ApplicationContext ctx = null;
       private LoginDao loginDao= null;
       private LoginService loginService = null;
       {
              ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
              loginDao =(LoginDao) ctx.getBean("loginDao");
              loginService =(LoginService)ctx.getBean("loginService");
       }
       @Test
       void testLoginDaoFindPasswordByusername() {
              System.out.println(loginDao.findPasswordByusername("11111"));
              //asdf
       }
       @Test
       void testLoginDaoUpdatePassword() {
              loginDao.updatePassword("11111", "pp");
       }
       @Test
       void testLoginServiceImpl() {
              loginService.changePassword("11111", "ppd");
       }
}

applicationContext.xml

db.properties

见Spring对JDBC的支持

LoginService

package com.spring.tx;
 
import org.springframework.stereotype.Service;
 
public interface LoginService {
       void changePassword(String username,String password);
}

LoginServiceImpl

package com.spring.tx;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
 
@Service("loginService")
public class LoginServiceImpl implements LoginService{
 
       @Autowired
       private LoginDao loginDao;
       /*添加事务注解,保证事务原子性
       1.使用propagation指定事务的传播行为,即当前事务方法被另一个事务调用时如何使用事务
       默认REQUIRED,即使用同一个事务
       REQUIRED_NEW,使用各自的事务
       2.使用isolation指定事物的隔离机制,最常用READ_COMMITTED读提交后的数据
       3.默认情况下Spring的声明式事务对所有的运行时异常回滚,也可以通过相应的属性进行设置。
       通常情况下默认即可
       4.使用readOnly指定事务是否为只读,表示事务只读取而不更新,可以进行优化
       5.使用timeout指定强制回滚之前事务可以占用的时间
       */
       @Transactional(propagation=Propagation.REQUIRED,
                     isolation=Isolation.READ_COMMITTED,readOnly=false,
                     timeout=3)
       @Override
       public void changePassword(String username, String password) {
              String p = loginDao.findPasswordByusername(username);
              if(!p.equals(password)) {
                     loginDao.updatePassword(username, password);
                     System.out.println(loginDao.findPasswordByusername(username));
              }else {
                     System.out.println("密码重复");
              }
              //其他各种操作
       }
      
 
}

LoginDao

package com.spring.tx;
 
public interface LoginDao {
       /**
        * 根据用户名查找密码
        */
       public String findPasswordByusername(String username);
       /**
        * 根据用户名和新密码进行修改
        */
       public void updatePassword(String username,String password);
 
}

LoginDaoImpl

package com.spring.tx;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
 
@Repository("loginDao")
public class LoginDaoImpl implements LoginDao{
       @Autowired
       private JdbcTemplate jdbcTemplate;
 
       @Override
       public String findPasswordByusername(String username) {
              String sql = "select password from login where username = ?";
              return jdbcTemplate.queryForObject(sql, String.class,username);
       }
 
       @Override
       public void updatePassword(String username, String password) {
              String sql = "update login set password = ? where username = ?";
              jdbcTemplate.update(sql,password,username);
       }
}

基于xml文件

SpringTransactionTest

package com.spring.tx.xml;
 
import static org.junit.jupiter.api.Assertions.*;
 
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
class SpringTransactionTest {
 
       private ApplicationContext ctx = null;
       private LoginDao loginDao= null;
       private LoginService loginService = null;
       {
              ctx = new ClassPathXmlApplicationContext("applicationContext-tx-xml.xml");
              loginDao =(LoginDao)ctx.getBean("loginDao");
              loginService =(LoginService)ctx.getBean("loginService");
       }
       @Test
       void testLoginDaoFindPasswordByusername() {
              System.out.println(loginDao.findPasswordByusername("11111"));
       }
       @Test
       void testLoginDaoUpdatePassword() {
              loginDao.updatePassword("11111", "pp");
       }
       @Test
       void testLoginServiceImpl() {
              loginService.changePassword("11111", "pod");
       }
}

db.properties同上

applicationContext-tx-xml.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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="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">
             
       <context:component-scan base-package="com.spring"></context:component-scan>
      
       <!-- 导入资源文件 -->
       <context:property-placeholder location="classpath:db.properties"/>
       <!-- 配置C3P0数据源 -->
       <bean id="datasSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
              <property name="user" value="${jdbc.user}"></property>
              <property name="password" value="${jdbc.password}"></property>
              <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
              <property name="driverClass" value="${jdbc.driverClass}"></property>
                    
              <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>     
              <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>     
      
       </bean>
       <!-- 配置Spring的JdbcTemplate -->
       <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
              <property name="dataSource" ref="datasSource"></property>
       </bean>
       <!-- 配置bean -->
       <bean id="loginDao" class="com.spring.tx.xml.LoginDaoImpl">
              <property name="jdbcTemplate" ref="jdbcTemplate"></property>
       </bean>
      
       <bean id="loginService" class="com.spring.tx.xml.LoginServiceImpl">
              <property name="loginDao" ref="loginDao"></property>
       </bean>
      
       <!-- 1.配置事务管理器 -->
       <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="datasSource"></property>
       </bean>
       <!-- 2.配置事务属性 -->
       <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
              <tx:attributes>
                     <!-- 根据方法名指定事务属性 -->
                     <tx:method name="changePassword" propagation="REQUIRED"/>
                     <tx:method name="get*" read-only="true"/>
                     <tx:method name="find*" read-only="true"/>
                     <tx:method name="*"/>
              </tx:attributes>
       </tx:advice>
       <!-- 配置事务切入点,以便把事务切入点和事务属性关联起来 -->
        <aop:config>
             <aop:pointcut expression="execution(* com.spring.tx.xml.LoginService.*(..))" id="txPointCut"/>
             <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
        </aop:config>
        
</beans>

LoginDao

package com.spring.tx.xml;
 
public interface LoginDao {
       /**
        * 根据用户名查找密码
        */
       public String findPasswordByusername(String username);
       /**
        * 根据用户名和新密码进行修改
        */
       public void updatePassword(String username,String password);
 
}

LoginDaoImpl

package com.spring.tx.xml;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
 
 
public class LoginDaoImpl implements LoginDao{
      
       private JdbcTemplate jdbcTemplate;
      
       public JdbcTemplate getJdbcTemplate() {
              return jdbcTemplate;
       }
 
       public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
              this.jdbcTemplate = jdbcTemplate;
       }
 
       @Override
       public String findPasswordByusername(String username) {
              String sql = "select password from login where username = ?";
              return jdbcTemplate.queryForObject(sql, String.class,username);
       }
 
       @Override
       public void updatePassword(String username, String password) {
              String sql = "update login set password = ? where username = ?";
              jdbcTemplate.update(sql,password,username);
       }
}

LoginService

package com.spring.tx.xml;
 
import org.springframework.stereotype.Service;
 
public interface LoginService {
       void changePassword(String username,String password);
}

LoginServiceImpl

package com.spring.tx.xml;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
 
 
public class LoginServiceImpl implements LoginService{
 
 
       private LoginDao loginDao;
      
       public LoginDao getLoginDao() {
              return loginDao;
       }
 
       public void setLoginDao(LoginDao loginDao) {
              this.loginDao = loginDao;
       }
 
       /*添加事务注解,保证事务原子性
       1.使用propagation指定事务的传播行为,即当前事务方法被另一个事务调用时如何使用事务
       默认REQUIRED,即使用同一个事务
       REQUIRED_NEW,使用各自的事务
       2.使用isolation指定事物的隔离机制,最常用READ_COMMITTED读提交后的数据
       3.默认情况下Spring的声明式事务对所有的运行时异常回滚,也可以通过相应的属性进行设置。
       通常情况下默认即可
       4.使用readOnly指定事务是否为只读,表示事务只读取而不更新,可以进行优化
       5.使用timeout指定强制回滚之前事务可以占用的时间
       */
       @Transactional(propagation=Propagation.REQUIRED,
                     isolation=Isolation.READ_COMMITTED,readOnly=false,
                     timeout=3)
       @Override
       public void changePassword(String username, String password) {
              String p = loginDao.findPasswordByusername(username);
              if(!p.equals(password)) {
                     loginDao.updatePassword(username, password);
                     System.out.println(loginDao.findPasswordByusername(username));
              }else {
                     System.out.println("密码重复");
              }
              //其他各种操作
       }
      
 
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值