前言

事务的提出是为了保证一组操作的原子性,让这组操作要么全部成功,要么全部不成功,不成功的话,所有操作回滚到事务开始之前的状态。这在很多地方都讲的很清楚了。mysql本身提供了事务,jdbc也提供了事务,hibernate,mybatis这样的ORM框架也提供了事务机制。

值得注意的是,mysql的事务编程依赖于关键字(begin,commit,rollback);jdbc的事务编程依赖于connection连接对象(connection.setAutoCommit(false),connection.commit,connection.rollback);hibernate的事务机制依赖于session对象(transaction=session.beginTransaction(),transaction.commit(),transaction.rollback())。也就是说,不管是数据库本身也好,还是数据库封装框架也好,关于事务的处理都有自己的一套体系,各自为政。

更为严重的是,这些事务的操作和业务逻辑代码纠缠在一起,每段业务代码前后都要加上事务操作。实际使用中,如果不能通过合适的方式将事务管理的代码和业务逻辑代码进行逻辑上的隔离,将直接导致业务代码和事务代码的可重用性降低。

Spring的事务管理机制提供了更高层次的抽象来帮助我们隔离事务和业务逻辑两方面的紧耦合。Spring框架已经实现了大多数关于数据库事务的操作(如jdbc,hibernate等),只需要配置事务bean对象,然后使用Spring的AOP编程模式将事务“切入”到业务逻辑代码。这样,既完美解耦,又整洁代码。

正文

一,Spring事务的3个主要接口

  1. PlatformTransactionManager: 平台事务管理器,spring要管理事务,必须使用事务管理器
    进行事务配置时,必须配置事务管理器。
  2. TransactionDefinition:事务详情(事务定义、事务属性),spring用于确定事务具体详情,
    例如:隔离级别、是否只读、超时时间 等 进行事务配置时,必须配置详情。spring将配置项封装到该对象实例。
  3. TransactionStatus:事务状态,spring用于记录当前事务运行状态。例如:是否有保存点,事务是否完成。spring底层根据状态进行相应操作。

其中,PlatformTransactionManager是用来生成事务的,其他两个是为它服务的。

PlatformTransactionManager接口有很多实现类,这些实现类就是具体的数据库操作啦,常见的有下面2种。

  • DataSourceTransactionManager : jdbc开发时事务管理器,采用JdbcTemplate
  • HibernateTransactionManager:hibernate开发时事务管理器,整合hibernate

二,经典的转账案例

该案例使用jdbc编程,并使用DataSourceTransactionManager生成事务。

1,dao和service类

package com.jimmy.dao;

import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class Dao extends JdbcDaoSupport{
    public void out(String name, double money) {
        this.getJdbcTemplate().update("update account set money=money-? where username=?", money,name);
    }
    public void in(String name, double money) {
        this.getJdbcTemplate().update("update account set money=money+? where username=?", money,name);
    }
}
package com.jimmy.service;

import com.jimmy.dao.Dao;

public class Service {
    private Dao dao;

    public void setDao(Dao dao) {
        this.dao = dao;
    }

    public void zhuanzhang(String outer,String inner,double money) {
        dao.out(outer, money);
        int i=1/0;   // 模拟转账过程中出现异常
        dao.in(inner, money);
    }
}

2,写配置文件

配置业务对象,依赖注入,配置事务对象,AOP操作都在配置文件中体现。

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

<!-- 读取property文件 -->
    <context:property-placeholder location="classpath:db.properties"/>

<!-- 生成c3p0连接池 -->
    <bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.DriverClass}"></property>
        <property name="jdbcUrl" value="${jdbc.URL}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>     
    </bean>

<!-- 生成jdbcTemplate,并注入连接池 -->
    <bean id="jdbcTemplateId" class="com.jimmy.dao.Dao">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean>

<!-- 生成service对象,并注入dao -->
    <bean id="serviceId" class="com.jimmy.service.Service">
        <property name="dao" ref="jdbcTemplateId"></property>
    </bean>

<!-- aop编程为切入点加事务 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.jimmy.service.Service.*(..))" id="pointcutId"/>
        <aop:advisor advice-ref="txAdviceId" pointcut-ref="pointcutId"/>
    </aop:config>

<!-- 生成事务对象,并注入c3p0连接池 -->
    <bean id="txManagerId" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSourceId"></property>
    </bean> 

<!-- 对生成的事务对象配置属性,如:传播行为,隔离类型,超时时间等 -->
    <tx:advice id="txAdviceId" transaction-manager="txManagerId">
        <tx:attributes>
            <tx:method name="zhuanzhang" isolation="DEFAULT" propagation="REQUIRED" timeout="20"/>
        </tx:attributes>
    </tx:advice>
</beans>

db.properties文件

jdbc.DriverClass=com.mysql.jdbc.Driver
jdbc.URL=jdbc:mysql:///user
jdbc.username=root
jdbc.password=123456

3,测试

package com.jimmy.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.jimmy.service.Service;

public class Test1 {
    @Test
    public void test1(){
        String xmlpath = "classpath:applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlpath);

        Service service = (Service) applicationContext.getBean("serviceId");
        service.zhuanzhang("jimmy", "angela", 100);
    }
}

上面代码运行会报错(除0了),但是数据库的数据不会发生改变,因为我们对转账函数加了事务。回过头来看我们的业务逻辑代码,干干净净,清清楚楚。不像以前一样,事务代码放到业务代码前后,整个既紧耦合,又不利于维护。

总结

Spring事务机制把事务从业务代码中分离出来,这对于编码非常有帮助。当然了,Spring事务机制不仅提供了jdbc的事务分离,还提供了hibernate等的事务分离,我们以后再说。