Spring事务管理入门

事务概念

事务指逻辑上的一组操作,这组操作要么全部成功,要么全部失败.

eg: 假设有张三和李四2个人,张三要给李四转账1000元,那数据库里需要执行的操作是张三扣1000元,李四增加1000元,如果张三扣了1000元突然断电了,就导致“李四增加1000”的程序没有执行,那李四岂不是很亏?为了杜绝这种情况,引进了事务的概念,也就是要求“张三-1000元,李四+1000元”的操作必须全部完成或者全部失败(失败的话事务会回滚),不能一个完成一个失败。

事务特性

事务有四大特性称为ACID.

 原子性好理解,和前面说的一个意思。

一致性是指事务处理前后数据的完整性必须保持一致.

隔离性指多用户并发访问数据库时,用户的事务不能被其它事务干扰,多个并发事务要相互隔离开.

持久性就是指一个事务一旦被提交,对数据库中数据的改变就是永久性的,即使数据库发生故障也不应该对其任何影响.

 事务管理接口

隔离级别

如果不考虑隔离性,会引发如下的安全问题: 1.脏读。 一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。 2.不可重复读。 在同一个事务中,多次读取同一数据返回的结果有所不同。 3.幻读。 一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。

详细可以参考 https://blog.csdn.net/starlh35/article/details/76445267

 

(MySQL底层默认使用REPEATABLE_READ级别,Oracle默认的事务隔离级别为READ_COMMITTED) 

传播行为

事务一般都是在业务层(service) ,传播行为解决业务层不同方法间的调用问题。

比如业务层2个方法a()和b(),a()调用了b().

如果传播行为是PROPAGATION_REQUIRED,a()和b()都在一个事务中,

如果是PROPAGATION_REQUIRES_NEW,a()和b()在2个事务中,而且2个事务独立

如果是PROPAGATION_NESTED,则a()和b()在2个事务中,Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。

详情参考 http://blog.sina.com.cn/s/blog_4b5bc0110100z7jr.html

事务状态

TransactionDefinition中定义了基本的事务属性,PlatformTransactionManager通过getTransaction()拿到事务定义,根据事务状态进行事务的管理。

(摘录自 https://www.cnblogs.com/ysocean/p/7617620.html#_label4

事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:

  

  TransactionDefinition 接口方法如下:

  

实战案例

(事务管理分为编程式事务管理和声明式事务管理,具体见案例)

创建java web项目

在mydb数据库中创建数据表

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `money` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', 'aaa', '1000');
INSERT INTO `account` VALUES ('2', 'bbb', '1000');
INSERT INTO `account` VALUES ('3', 'ccc', '1000');
 

 数据表记录了银行每个人编号,姓名,余额.

点击下载

引入项目需要的包

点击下载

写好spring配置文件。

properties文件:

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.2.107:3306/mydb
jdbc.username=root
jdbc.password=root

applicationContext.xml(spring配置文件)

<?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:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    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.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 引入jdbc.properties -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!-- 配置 Spring 的 dbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 配置DAO的类 -->
    <bean id="accountDao" class="com.test.spring.demo1.AccountDaoImpl"></bean>
    <!-- 配置业务层类 -->
    <bean id="accountService" class="com.test.spring.demo1.AccountServiceImpl"></bean>
    
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入连接池对象 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 配置事务管理的模板:Spring为了简化事务管理的代码而提供的类 -->
    <bean id="transactionTemplate" 
    class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
    
    <!-- 注解扫描 -->
    <context:component-scan base-package="com.test.spring"/>

  

</beans>

dao层

public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    // out账户减少money元
    @Override
    public void outMoney(String out, Double money) {
        String sql = "update account set money = money - ? where name = ? ";
        jdbcTemplate.update(sql,money,out);
    }

    // in 账户增加money元

    @Override
    public void inMoney(String in, Double money) {
        String sql = "update account set money = money + ? where name = ? ";
        jdbcTemplate.update(sql, money, in);
    }

}

Service层:

public class AccountServiceImpl implements AccountService {

    // 注入事务管理的模板
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    @Autowired
    private AccountDao accountDao;
    
    @Override
    public void transfer(final String out, final String in, final Double money) {
        // 编程式事务管理(实际开发很少使用)
        // 备注: 匿名内部类要使用外部方法的形参必须用final修饰
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                accountDao.outMoney(out, money);
                int i = 1 / 0;    // 会导致出现异常,事务会回滚
                accountDao.inMoney(in, money);                                
            }
        });
        
        
    }

}

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {    
    @Autowired
    private AccountService accountService;
    

   // aaa给bbb转账200
    @Test
    public void test() {                
        accountService.transfer("aaa", "bbb", 200d);
    }
}
 

结果分析:

如果在service层没有事务管理,结果会导致aaa账户减少200元,bbb账户不变。配置了事务管理出现异常事务会回滚数据库不会有任何变化。

声明式事务管理:基于TransactionProxyFactoryBean的方式

将spring配置文件改为

<?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:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    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.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 引入db.properties -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

  
    
    <!-- 配置DAO的类 -->
    <bean id="accountDao" class="com.test.spring.demo2.AccountDaoImpl"></bean>
    <!-- 配置业务层类 -->
    <bean id="accountService" class="com.test.spring.demo2.AccountServiceImpl"></bean>
    
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入连接池对象 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 配置业务层的代理 -->
    <bean id="accountServiceProxy" 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <!-- 配置目标对象 -->
        <property name="target" ref="accountService"/>
        <!-- 注入事务管理器 -->
        <property name="transactionManager" ref="transactionManager"/>
        <!-- 注入事务属性 -->
        <property name="transactionAttributes">
            <props>
                <!-- prop格式:
                * PROPAGATION    :事务的传播行为。
                * ISOLATION        :事务的隔离级别。
                * readOnly        :只读。
                * -Exception    :发生哪些异常回滚事务。
                * +Exception    :发生哪些异常事务不会滚。
                 -->
                 <!-- transfer开头的方法 -->
                <prop key="transfer*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
    
    <!-- 注解扫描 -->
    <context:component-scan base-package="com.test.spring"/>

  

</beans>

Service层改为

package cn.muke.spring.demo2;

/**
 * 转账案例的业务成实现类
 */
public class AccountServiceImpl implements AccountService {
    // 注入转账的DAO的类
    private AccountDao accountDao;

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

    /**
     * @param out    :转出的账号
     * @param in    :转入的账号
     * @param money    :转账的金额
     */
    public void transfer( String out,  String in,  Double money) {
                accountDao.outMoney(out, money);
                //int i =1/0;
                accountDao.inMoney(in, money);
    }

}
 

Test改为:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo2 {    
    @Resource(name="accountServiceProxy")
    private AccountService accountService;
    
    @Test
    public void test() {                
        accountService.transfer("aaa", "bbb", 200d);
    }
}

(备注:此方法实际开发很少用)

声明式事务管理:基于AspectJ的XML方式(常用)

只需要把spring配置文件改为

<?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:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    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.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 引入db.properties -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

  
    
    <!-- 配置DAO的类 -->
    <bean id="accountDao" class="com.test.spring.demo3.AccountDaoImpl"></bean>
    <!-- 配置业务层类 -->
    <bean id="accountService" class="com.test.spring.demo3.AccountServiceImpl"></bean>
    <!-- 配置 Spring 的 jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置事务管理器 -->
    <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>
            <!-- 
                propagation    :事务传播行为
                isolation    :事务隔离级别
                read-only    :只读
                rollback-for:发生哪些异常回滚
                no-rollback-for:发生哪些异常不回滚
                timeout        :过期信息
             -->
            <tx:method name="transfer*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置切面 -->
    <aop:config>
        <!-- 配置切入点 -->
        <!-- 第一个*:任意返回值,+:AccountService所有子类,*任意的方法,(..)任意的参数 -->
        <aop:pointcut expression="execution(* com.test.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
        <!-- 切面 -->
        <!-- advisor只配置1个切点,aspect配置多切点 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
    </aop:config>
    <!-- 注解扫描 -->
    <context:component-scan base-package="com.test.spring"/>

  

</beans>

基于注解的事务管理(常用) 

将配置文件改成

<?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:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    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.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 引入db.properties -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!-- 配置C3P0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

  
    
    <!-- 配置DAO的类 -->
    <bean id="accountDao" class="com.test.spring.demo4.AccountDaoImpl"></bean>
    <!-- 配置业务层类 -->
    <bean id="accountService" class="com.test.spring.demo4.AccountServiceImpl"></bean>
    <!-- 配置 Spring 的 jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入连接池对象 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 注解扫描 -->
    <context:component-scan base-package="com.test.spring.demo4"/>
    <!-- 开启注解事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

(主要是需要开启注解事务)

然后在需要事务管理的service层加上@Transactional注解,例如:

/**
 *@Transational注解中的属性: 
 * propagation    :事务的传播行为
 * isolation    :事务的隔离级别
 * readOnly        :只读
 * rollbackFor    :发生哪些异常回滚
 * noRollbackFor:发生哪些异常不回滚
 */

@Transactional(propagation=Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
    
    @Override
    public void transfer( String out,  String in,  Double money) {
            // 声明式事务管理
                accountDao.outMoney(out, money);
                //int i = 1 / 0;
                accountDao.inMoney(in, money);                                

    }

}
 

这样就完成了spring的基于注解的事务管理.

猜你喜欢

转载自blog.csdn.net/lmhlmh_/article/details/81530934