Java Spring-事务管理

2017-11-12 16:31:59

Spring的事务管理分为两种:

  • 编程式的事务管理:手动编写代码
  • 声明式的事务管理:只需要配置就可以

一、最初的环境搭建

public interface AccountDAO {
    public void out(String to,Double money);

    public void in(String from, Double money);
}

public class AccountDAOImpl extends JdbcDaoSupport implements AccountDAO {
    @Override
    public void out(String from, Double money) {
        String sql =  "update account  set money = money - ? where name = ?";
        this.getJdbcTemplate().update(sql, money,from);
    }

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

public interface AccountService {
    public void transfer(String from, String to, Double money);
}

public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDAO accountDAO;

    @Override
    public void transfer(String from, String to, Double money) {
        accountDAO.out(from,money);
        accountDAO.in(to,money);
    }
}

// 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:config4.xml")
public class TestDemo {
    @Resource(name = "accountservice")
    private AccountService accountService;

    @Test
    public void demo(){
        accountService.transfer("aaa","bbb",100d);
    }
}

二、手动式的事务管理

可以发现,在没有引入事务管理的时候,如果在转账的out和in之间出现了异常,那么就会导致转账的结果出错。所以我们需要引入事务管理技术。

Spring提供了事务管理的模板(工具类),可以方便我们对事务进行管理。

具体步骤:

  • 第一步:注册事务管理器
  • 第二步:注册事务模板类
  • 第三步:在业务层注入模板类
  • 第四步:在业务层代码上使用模板
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDAO accountDAO;

    @Resource(name = "transactionTemplate")
    private TransactionTemplate transactionTemplate;

    @Override
    public void transfer(String from, String to, Double money) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                accountDAO.out(from,money);
                accountDAO.in(to,money);
            }
        });
    }
}

XML的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans" 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">

    <!--配置连接池-->
    <!--<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">-->
    <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>-->
    <!--<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>-->
    <!--<property name="username" value="host"/>-->
    <!--<property name="password" value="hy1102"/>-->
    <!--</bean>-->

    <!-- 配置DBCP连接池 -->
    <!--<bean id="datasource" class=" org.apache.commons.dbcp.BasicDataSource ">-->
    <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>-->
    <!--<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>-->
    <!--<property name="username" value="host"/>-->
    <!--<property name="password" value="hy1102"/>-->
    <!--</bean>-->

    <!-- 引入该属性文件 -->
    <!--<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">-->
    <!--<property name="location" value="classpath:jdbc.properties"/>-->
    <!--</bean>-->

    <!-- 使用 context 标签引入属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--  配置 c3p0 连接池 -->
    <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 定义模板 -->
    <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource"/>
    </bean>

    <bean id="userdao" class="spring3.UserDao">
        <property name="jdbcTemplate" ref="jdbctemplate"/>
    </bean>

    <!--业务层-->
    <bean id="accountservice" class="spring4.AccountServiceImpl"/>

    <!--持久层-->
    <bean id="accountDao" class="spring4.AccountDAOImpl">
        <!--事实上可以直接注入连接池来创建模板-->
        <property name="dataSource" ref="datasource"/>
    </bean>

    <!--配置事务管理器-->
    <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"/>
    </bean>


</beans>

三、声明式的事务管理

手动编码方式类似于对transfer方法进行增强,所以考虑代理Service对象。

  • 基于原始的TransactionProxyFactoryBean
// 业务代码
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDAO accountDAO;

    @Override
    public void transfer(String from, String to, Double money) {
        accountDAO.out(from,money);
        accountDAO.in(to,money);
    }
}

// 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:config4.xml")
public class TestDemo {
    @Resource(name = "accountServiceProxy")
    private AccountService accountService;

    @Test
    public void demo(){
        accountService.transfer("aaa","bbb",100d);
    }
}

XML的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans" 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">

    <!--配置连接池-->
    <!--<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">-->
    <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>-->
    <!--<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>-->
    <!--<property name="username" value="host"/>-->
    <!--<property name="password" value="hy1102"/>-->
    <!--</bean>-->

    <!-- 配置DBCP连接池 -->
    <!--<bean id="datasource" class=" org.apache.commons.dbcp.BasicDataSource ">-->
    <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>-->
    <!--<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>-->
    <!--<property name="username" value="host"/>-->
    <!--<property name="password" value="hy1102"/>-->
    <!--</bean>-->

    <!-- 引入该属性文件 -->
    <!--<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">-->
    <!--<property name="location" value="classpath:jdbc.properties"/>-->
    <!--</bean>-->

    <!-- 使用 context 标签引入属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--  配置 c3p0 连接池 -->
    <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 定义模板 -->
    <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource"/>
    </bean>

    <bean id="userdao" class="spring3.UserDao">
        <property name="jdbcTemplate" ref="jdbctemplate"/>
    </bean>

    <!--业务层-->
    <bean id="accountservice" class="spring4.AccountServiceImpl"/>

    <!--持久层-->
    <bean id="accountDao" class="spring4.AccountDAOImpl">
        <!--事实上可以直接注入连接池来创建模板-->
        <property name="dataSource" ref="datasource"/>
    </bean>

    <!--配置事务管理器-->
    <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"/>
    </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 key="transfer">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

</beans>

prop格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception
* 顺序:传播行为、隔离级别、事务是否只读、发生哪些异常可以回滚事务(所有的异常都回滚)、发生了哪些异常不回

<prop key="transfer">PROPAGATION_REQUIRED,readonly</prop>
<prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop>
  • 基于切面自动代理

上面的方法每次都要手动生成代理,显然是不太合适的,所以可以使用基于切面的自动代理。

public interface AccountDAO {
    public void out(String to,Double money);

    public void in(String from, Double money);
}

public class AccountDAOImpl extends JdbcDaoSupport implements AccountDAO {
    @Override
    public void out(String from, Double money) {
        String sql =  "update account  set money = money - ? where name = ?";
        this.getJdbcTemplate().update(sql, money,from);
    }

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

public interface AccountService {
    public void transfer(String from, String to, Double money);
}

public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDAO accountDAO;

    @Override
    public void transfer(String from, String to, Double money) {
        accountDAO.out(from,money);
        accountDAO.in(to,money);
    }
}

//测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:config5.xml")
public class TestDemo {
    @Resource(name = "accountservice")
    private AccountService accountService;

    @Test
    public void demo(){
        accountService.transfer("aaa","bbb",100d);
    }
}

XML的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.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">

    <!-- 使用 context 标签引入属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--  配置 c3p0 连接池 -->
    <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 定义模板 -->
    <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource"/>
    </bean>

    <bean id="userdao" class="spring3.UserDao">
        <property name="jdbcTemplate" ref="jdbctemplate"/>
    </bean>

    <!--业务层-->
    <bean id="accountservice" class="spring4.AccountServiceImpl"/>

    <!--持久层-->
    <bean id="accountDao" class="spring4.AccountDAOImpl">
        <!--事实上可以直接注入连接池来创建模板-->
        <property name="dataSource" ref="datasource"/>
    </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>
            <!--
            isolation:DEFAULT :事务的隔离级别.
            propagation :事务的传播行为.
            read-only :false.不是只读
            timeout :-1
            no-rollback-for :发生哪些异常不回滚
            rollback-for :发生哪些异常回滚事务
            -->
            <tx:method name="transfer" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>

    <!-- aop配置定义切面和切点的信息 -->
    <aop:config>
        <!-- 定义切点:哪些类的哪些方法应用增强 -->
        <aop:pointcut expression=" execution(* spring4.AccountService+.*(..)) " id="mypointcut"/>
        <!-- 定义切面: -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="mypointcut"/>
    </aop:config>

</beans>
  • 基于注解的事务配置

具体步骤:

  • 第一步:事务管理器
  • 第二步:注解事务
  • 第三步:在Service上使用注解

* 注解中有属性值:

* isolation

* propagation

* readOnly

...

  • 第四步:测试
@Transactional
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDAO accountDAO;

    @Override
    public void transfer(String from, String to, Double money) {
        accountDAO.out(from,money);
        // int i = 1/0;
        accountDAO.in(to,money);
    }
}

XML配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.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">

    <!-- 使用 context 标签引入属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--  配置 c3p0 连接池 -->
    <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 定义模板 -->
    <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource"/>
    </bean>

    <bean id="userdao" class="spring3.UserDao">
        <property name="jdbcTemplate" ref="jdbctemplate"/>
    </bean>

    <!--业务层-->
    <bean id="accountservice" class="spring4.AccountServiceImpl"/>

    <!--持久层-->
    <bean id="accountDao" class="spring4.AccountDAOImpl">
        <!--事实上可以直接注入连接池来创建模板-->
        <property name="dataSource" ref="datasource"/>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 需要注入连接池,通过连接池获得连接 -->
        <property name="dataSource" ref="datasource"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

比较来看,最后的基于注解的方式是最容易,也是代码量最少的。

原文地址:https://www.cnblogs.com/hyserendipity/p/7822391.html