spring事务管理方式

今天总结下spring对于事务的支持。我只会把每一步的步骤都写清楚,如果有些需要着重说的,我会以注解的方式写在代码里。

spring的事务支持主要分为两大类:
    1. 编程式事务
            通过编码的方式来实现事务的管理(很少用).

    2.声明式事务
            又分为三种。
                1. 服务层代理类的方式(很少用)
                2. aop的方式实现(最常用)
                3. 注解方式实现(常用)

准备工作

我们的场景为转账,从AAA帐号转200元到BBB的帐号,
我们在AAA转出200元后,使代码发生异常(1/0),如果数据中没有发生AAA钱转出(因为发生了异常,事务回滚。)则事务生效。
[idea创建maven工程](http://blog.csdn.net/baibinboss/article/details/62897695)
在pom.xml文件中加入我们的依赖,清单如下:
pom.xml内容
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.baibin</groupId>
    <artifactId>transaction</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!--aop支持,spring的aop支持是依赖aop联盟的-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <!--阿里开源的数据库链接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.16</version>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--spring上下文模块-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.6.RELEASE</version>
        </dependency>
        <!--spring数据库操作-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.6.RELEASE</version>
        </dependency>
        <!--spring测试集成-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.6.RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
配置数据库链接信息
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=password
数据库建表语句
CREATE TABLE `t_account` (
  `id` varchar(32) NOT NULL DEFAULT '',
  `name` varchar(32) DEFAULT NULL,/*帐号*/
  `account` decimal(10,0) DEFAULT NULL,/*钱*/
  PRIMARY KEY (`id`)
);
INSERT INTO `t_account` VALUES ('1', 'AAA', '1000');
INSERT INTO `t_account` VALUES ('2', 'BBB', '1000');
INSERT INTO `t_account` VALUES ('3', 'CCC', '1000');

编程式事务

第一步:配置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"
       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">
    <!--扫描配置的注解-->
    <context:component-scan base-package="com.baibin.demo1"/>
    <!--将所有的类路径下面的配置文件交给spring管理-->
    <context:property-placeholder location="classpath*:*.properties"/>
    <!--数据库操作-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg>
            <ref bean="dataSource"></ref>
        </constructor-arg>
    </bean>
    <!--数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.mysql.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--事务管理器
        这里因为我们是用最基础的方式来实现对数据库操作的,所以我们使用DataSourceTransactionManager
    -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--事务模版,我们通过这个对象来进行编程式事务操作-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
</beans>

第二步:服务层代码:

package com.baibin.demo1.service;

/**
 * 接口代码
 */
public interface TransferService {
    /**
     * 转账代码
     * @param up 转出帐号
     * @param down 转入帐号
     * @param account 额度
     */
    void transferAccounts(final String up,final String down,final double account);
}
package com.baibin.demo1.service;

import com.baibin.demo1.dao.TransferDAO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;

/**
 * 实现类代码
 */
@Service
public class TransferServiceImpl implements TransferService {
    @Resource
    private TransferDAO transferDAO;
    @Resource
    private TransactionTemplate transactionTemplate;

    /**
     * 转账代码,因为需要在内部类中使用参数,所以需要写成final修饰的
     *
     * @param up      转出帐号
     * @param down    转入帐号
     * @param account 额度
     */
    public void transferAccounts(final String up, final String down, final double account) {
        //转出钱,转入钱,他们应该一起成功,或者一起失败,我们用 1/0 来模拟发生意外。
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                //转出
                transferDAO.expenditure(up, account);
                //发生异常
                int i = 1/0;
                //转入
                transferDAO.income(down, account);
            }
        });
    }
}

第三步:持久层代码

package com.baibin.demo1.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * 持久层
 */
@Repository
public class TransferDAO {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
     * 进账
     *
     * @param down
     * @param account
     */
    public void income(String down, double account) {
        String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT+? WHERE NAME=?";
        jdbcTemplate.update(sql, account, down);
    }

    /**
     * 转出
     *
     * @param up
     * @param account
     */
    public void expenditure(String up, double account) {
        String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT-? WHERE NAME=?";
        jdbcTemplate.update(sql, account, up);
    }
}

第四步:单元测试

package com.baibin.demo1.test;

import com.baibin.demo1.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;

/**
 * 编程试事务,在使用所有注解前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
 */
@RunWith(value = SpringJUnit4ClassRunner.class)
/**
 * 加载spring配置文件
 */
@ContextConfiguration(locations = {"classpath:beans1.xml"})
public class TransferServiceImplTest {
    @Autowired
    private TransferService transferService;
    @Test
    public void transferAccounts() throws Exception {
        /**
         * AAA帐号转账200到BBB帐号
         */
        transferService.transferAccounts("AAA","BBB",200D);
    }
}
编程式事务需要我们自己来写代码,比较麻烦,一般很少用。

声明式事务之服务层代理类的方式

第一步:配置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"
       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">
    <!--扫描配置的注解-->
    <context:component-scan base-package="com.baibin.demo2"/>
    <!--将所有的类路径下面的配置文件交给spring管理-->
    <context:property-placeholder location="classpath*:*.properties"/>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg>
            <ref bean="dataSource"></ref>
        </constructor-arg>
    </bean>
    <!--数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.mysql.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="transferDAO" class="com.baibin.demo2.dao.TransferDAO">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <bean id="transferService" class="com.baibin.demo2.service.TransferServiceImpl">
        <property name="transferDAO" ref="transferDAO"></property>
    </bean>
    <!--事务代理类,我们在使用服务层对象的时候不在直接注入transferService而是注入我们的代理类-->
    <bean id="transferServiceProxy"
          class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <!--为代理类注入事务管理器-->
        <property name="transactionManager" ref="transactionManager"></property>
        <!--代理的对象-->
        <property name="target" ref="transferService"></property>
        <!--事务配置-->
        <property name="transactionAttributes">
            <props>
                <!--任意方法名
                PROPAGATION_REQUIRED:需要一个事务,如果没有则创建新的事务-->
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
</beans>

第二步:服务层代码:

package com.baibin.demo2.service;

import com.baibin.demo1.service.TransferService;
import com.baibin.demo2.dao.TransferDAO;

/**
 * 服务层代码
 */
public class TransferServiceImpl implements TransferService {
    private TransferDAO transferDAO;

    public TransferDAO getTransferDAO() {
        return transferDAO;
    }

    /**
     * 需要set方法注入bean
     * @param transferDAO
     */
    public void setTransferDAO(TransferDAO transferDAO) {
        this.transferDAO = transferDAO;
    }

    public void transferAccounts(String up, String down, double account) {
        transferDAO.expenditure(up, account);
        /*手动触发异常*/
        int i = 1/0;
        transferDAO.income(down, account);
    }
}

第三步:持久层代码

package com.baibin.demo1.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * 持久层
 */
@Repository
public class TransferDAO {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
     * 进账
     *
     * @param down
     * @param account
     */
    public void income(String down, double account) {
        String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT+? WHERE NAME=?";
        jdbcTemplate.update(sql, account, down);
    }

    /**
     * 转出
     *
     * @param up
     * @param account
     */
    public void expenditure(String up, double account) {
        String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT-? WHERE NAME=?";
        jdbcTemplate.update(sql, account, up);
    }
}

第四步:单元测试

package com.baibin.demo2.test;

import com.baibin.demo1.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

/**
 * 在使用所有注解前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
 */
@RunWith(value = SpringJUnit4ClassRunner.class)
/**
 * 加载spring配置文件
 */
@ContextConfiguration(locations = {"classpath:beans2.xml"})
public class TransferServiceImplTest {
    /**
     * 这里需要注意,我们不在直接注入TransferService  而是注入transferServiceProxy
     */
    @Resource(name = "transferServiceProxy")
    private TransferService transferService;
    @Test
    public void transferAccounts() throws Exception {
        transferService.transferAccounts("AAA","BBB",200);
    }
}
    代理类的方式呢,优点是对java代码没有了污染,缺点就是需要配置的工作太多了。

声明式事务之aop切面方式

第一步:配置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: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.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">
    <!--扫描配置的注解-->
    <context:component-scan base-package="com.baibin.demo3"/>
    <!--将所有的类路径下面的配置文件交给spring管理-->
    <context:property-placeholder location="classpath*:*.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.mysql.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager" >
        <tx:attributes>
            <!--name里写事务管理的匹配模式,*是通配符,代表所有的方法 *Save代表Save名结束的方法-->
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!--配置aop切入点-->
    <aop:config>
        <!--aop切入的规则-->
        <aop:pointcut id="allMethod" expression="execution(* com.baibin.demo3..*.*(..))"></aop:pointcut>
        <!--指向事务通知-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="allMethod"></aop:advisor>
    </aop:config>
</beans>

第二步:服务层代码:

package com.baibin.demo3.service;

import com.baibin.demo1.service.TransferService;
import com.baibin.demo3.dao.TransferDAO;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * 服务层代码
 */
@Service
public class TransferServiceImpl implements TransferService {
    @Resource
    private TransferDAO transferDAO;

    public void transferAccounts(String up, String down, double account) {
        transferDAO.expenditure(up, account);
        /*手动触发异常*/
        int i = 1 / 0;
        transferDAO.income(down, account);
    }
}

第三步:持久层代码

package com.baibin.demo3.dao;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

/**
 * Created by ibm on 2017/3/22.
 */
@Repository
public class TransferDAO {

    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 进账
     *
     * @param down
     * @param account
     */
    public void income(String down, double account) {
        String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT+? WHERE NAME=?";
        jdbcTemplate.update(sql, account, down);
    }

    /**
     * 转出
     *
     * @param up
     * @param account
     */
    public void expenditure(String up, double account) {
        String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT-? WHERE NAME=?";
        jdbcTemplate.update(sql, account, up);
    }
}

第四步:单元测试

package com.baibin.demo3.test;

import com.baibin.demo1.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

/**
 * 在使用所有注解前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
 */
@RunWith(value = SpringJUnit4ClassRunner.class)
/**
 * 加载spring配置文件
 */
@ContextConfiguration(locations = "classpath:beans3.xml")
public class TransferServiceImplTest {
    @Resource
    private TransferService transferService;
    @Test
    public void transferAccounts() throws Exception {
        transferService.transferAccounts("AAA","BBB",200D);
    }
}
    aop的方式我个人觉得是最佳的方式,一个项目配置一个就可以完成所有的事务管理了(强烈建议)。

声明式事务之注解方式

第一步:配置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: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/tx 
       http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--扫描配置的注解-->
    <context:component-scan base-package="com.baibin.demo4"/>
    <!--将所有的类路径下面的配置文件交给spring管理-->
    <context:property-placeholder location="classpath*:*.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.mysql.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--这里需要启动注解解析,需要设置事务管理器,如果不写则会默认为transactionManager-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

第二步:服务层代码:

package com.baibin.demo4.service;

import com.baibin.demo1.service.TransferService;
import com.baibin.demo4.dao.TransferDAO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * 服务层代码
 */
@Service
public class TransferServiceImpl implements TransferService {
    @Resource
    private TransferDAO transferDAO;

    /**
     * 这里需要使用 @Transactional注解,propagation是指传播方式,具体的自己可以查下,这次就不在博客里赘述了。
     * @param up 转出帐号
     * @param down 转入帐号
     * @param account 额度
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void transferAccounts(String up, String down, double account) {
        transferDAO.expenditure(up, account);
        transferDAO.income(down, account);
    }
}

第三步:持久层代码

package com.baibin.demo4.dao;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

/**
 * Created by ibm on 2017/3/22.
 */
@Repository
public class TransferDAO {

    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 进账
     *
     * @param down
     * @param account
     */
    public void income(String down, double account) {
        String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT+? WHERE NAME=?";
        jdbcTemplate.update(sql, account, down);
    }

    /**
     * 转出
     *
     * @param up
     * @param account
     */
    public void expenditure(String up, double account) {
        String sql = "UPDATE T_ACCOUNT SET ACCOUNT=ACCOUNT-? WHERE NAME=?";
        jdbcTemplate.update(sql, account, up);
    }
}

第四步:单元测试

package com.baibin.demo4.test;

import com.baibin.demo1.service.TransferService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

/**
 * 在使用所有注解前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
 */
@RunWith(value = SpringJUnit4ClassRunner.class)
/**
 * 加载spring配置文件
 */
@ContextConfiguration(locations = "classpath:beans4.xml")
public class TransferServiceImplTest {
    @Resource
    private TransferService transferService;

    @Test
    public void transferAccounts() throws Exception {
        transferService.transferAccounts("AAA", "BBB", 200D);
    }
}
    注解的方式也比较不错,不过需要在每个类上面都写,还是建议大家使用aop的方式。

总结:

    今天写了下java的spring提供的事务解决方案,我写的代码比较缀余,为的是大家可以拷过去就能用,事务还有一些隔离级别,
传播方式相关的内容,下次和大家一起分享。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值