美文网首页
Spring 声明式事务控制

Spring 声明式事务控制

作者: SheHuan | 来源:发表于2020-01-15 14:57 被阅读0次

Spring 的事务控制可以分为编程式和声明式两种,编程式需要开发者通过编写代码的方式来实现事务管理,并不好用。而声明式则不需要编码,只需开发者按照约定的规则配置即可,实际的事务管理由框架完成,更加方便灵活。接下来就是 Spring 声明式事务控制的使用介绍。

一、要实现的业务功能

业务代码的核心功能就是模拟银行转账功能,正常情况下转出、转入需要同时成功,如果转账过程中有异常导致转入或转出失败,需要进行事务回滚,防止账户余额异常。

例子中,dao层使用JdbcTemplate进行数据库操作,模拟账户查询和更新账户金额:

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    // 根据姓名查询账户
    public Account getAccountByName(String name) {
        String sql = "select * from account where name = ?";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class), name).get(0);
    }
    // 更新账户余额
    public void updateAccount(Account account) {
        String sql = "update account set name = ?, money = ? where id = ?";
        jdbcTemplate.update(sql, account.getName(), account.getMoney(), account.getId());
    }
}

service层则进行具体的转账业务,注意int s = 1/0;是用来模拟转账失败的场景,模拟正常转账时需要注释掉:

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void transfer(String name1, String name2, float money) {
        // 1.查询转出账户
        Account account1 = accountDao.getAccountByName(name1);
        // 2.查询转入账户
        Account account2 = accountDao.getAccountByName(name2);
        // 3.转出账户减少钱
        account1.setMoney(account1.getMoney() - money);
        // 4.转入账户增加钱
        account2.setMoney(account2.getMoney() + money);
        // 5.更新转出账户
        accountDao.updateAccount(account1);
        // 制造异常,使转账失败
        int s = 1/0;
        // 6.更新转入账户
        accountDao.updateAccount(account2);
    }
}

account表中已创建了两个默认账户:

Spring 声明式事务控制按照使用方式可以分为基于xml和注解两大类,这里只列举常用的使用方式,我们先看基于xml的:

二、使用tx、aop标签的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.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">
    <!--扫描指定包下使用了@Component、@Service、@Repository注解的类-->
    <context:component-scan base-package="com.shh.jdbcTemplate"/>

    <!--加载属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties" ignore-resource-not-found="true"/>

    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--Spring中基于xml的声明式事务配置开始-->
    <!--配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置事务的属性, name表示要对哪些方法使用事务-->
        <!--isolation:指定事务的隔离级别,默认DEFAULT,使用数据库的默认隔离级别-->
        <!--propagation:指定事务的传播行为,默认REQUIRED,表示一定会有事务(增删改使用),SUPPORTS(查询可使用)-->
        <!--read-only:指定是否是只读事务,默认为false,查询方法可设置为true-->
        <!--timeout:指定事务的超时时间,默认-1,不会超时,单位为秒-->
        <!--rollback-for:指定一个异常,当发生该异常时回滚事务,有其它异常时不回滚,默认没有值,任何异常都会回滚-->
        <!--no-rollback-for:指定一个异常,当发生该异常时不回滚事务,有其它异常时回滚事务,默认没有值,任何异常都会回滚-->
        <tx:attributes>
            <tx:method name="*"/>
            <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!--事务的AOP相关配置-->
    <aop:config>
        <!--定义切入点表达式-->
        <aop:pointcut id="pt" expression="execution(* com.shh.jdbcTemplate.service.impl.*.*(..))"/>
        <!--建立切入点表达式和事务的通知的关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>
    <!--Spring中基于xml的声明式事务配置结束-->
</beans>

比较重要的点都在注释中有写到,至于事务是如何开启、提交、回滚等操作都是由 Spring 完成的,目前需要我们关心的是配置事务控制的主要步骤:

  1. 配置事务管理器
  2. 配置事务的通知,这里需要用到事务管理器,然就是配置事务的属性,哪些方法会被匹配到,可以使用事务管理
  3. 使用Sping AOP 将切入点表达式和事务的同事关联起来,切入点的作用是指定哪些类中的方法会被进行事务控制增强

测试代码如下,从张三账户给李四账户转100:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:jdbc-template.xml"})
public class JdbcTemplateTest {
    @Autowired
    private IAccountService accountService;

    @Test
    public void transfer() {
        accountService.transfer("张三", "李四", 100);
    }
}

先将之前的int s = 1/0;放开,模拟转账失败,转账时会抛出异常,事务回滚,两个账户的余额不会变化:



再将int s = 1/0;注释掉,执行测试方法,则转账成功,账户余额会有变化:

经测试已经实现了我们的目的,接下来看第二种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"
       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.shh.jdbcTemplate"/>

    <!--加载属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties" ignore-resource-not-found="true"/>

    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--Spring中基于xml的声明式事务配置开始-->
    <!--配置事务拦截器,拦截哪些方法-->
    <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager" ref="transactionManager"/>
        <!--配置事务属性,key表示要对哪些方法使用事务,<prop>里边的是事务的属性 -->
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
                <prop key="get*">PROPAGATION_SUPPORTS,readOnly</prop>
                <prop key="query*">PROPAGATION_SUPPORTS,readOnly</prop>
                <prop key="find*">PROPAGATION_SUPPORTS,readOnly</prop>
                <prop key="select*">PROPAGATION_SUPPORTS,readOnly</prop>
            </props>
        </property>
    </bean>
    <!--指定事务拦截器要拦截哪些类-->
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames">
            <list>
                <!--对象在IoC容器中的名称,*是通配符-->
                <value>*Service</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>transactionInterceptor</value>
            </list>
        </property>
    </bean>
    <!--Spring中基于xml的声明式事务配置结束-->
</beans>

关键的地方就是使用TransactionInterceptor拦截器类,配置哪些方法要使用事务,以及事务的属性。使用BeanNameAutoProxyCreator类来指定拦截 Spring IoC 容器中的哪些对象,同时和TransactionInterceptor建立关联。

具体的测试和之前类似就不重复了。

使用注解的声明式事务配置相对简单些,可以xml+注解混合使用,也可以使用纯注解。

四、使用xml+注解的配置

这里 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: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.shh.jdbcTemplate"/>

    <!--加载属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties" ignore-resource-not-found="true"/>

    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--Spring中基于注解的声明式事务配置开始-->
    <!--开启Spring对基于注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!--在需要事务支持的类上添加@Transactional注解-->
    <!--Spring中基于注解的声明式事务配置结束-->
</beans>

然后就是在service层需要使用事务控制的类上添加@Transactional注解,这样类里边的方法会受到事务控制:

@Service("accountService")
@Transactional
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    public void transfer(String name1, String name2, float money) {
       ......
    }
}

@Transactional也可以配置事务的属性,例如:

@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

测试代码和之前相同。

五、使用全注解的配置

在使用xml+注解的配置中,xml中的配置,可以完全用注解的方式替代,实现全注解配置。

数据库配置文件jdbc.properties的加载、jdbcTemplatedataSourcebean对象的管理,可以用JdbcConfig类完成:

@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
    @Value("${database.driver}")
    private String driverClass;
    @Value("${database.url}")
    private String jdbcUrl;
    @Value("${database.username}")
    private String user;
    @Value("${database.password}")
    private String password;

    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean("dataSource")
    public ComboPooledDataSource createDataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driverClass);
        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setUser(user);
        dataSource.setPassword(password);
        return new ComboPooledDataSource();
    }
}

事务管理器transactionManager对象,可以用TransactionConfig类维护:

@Configuration
public class TransactionConfig {
    @Bean("transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

开启Spring对基于注解事务的支持<tx:annotation-driven transaction-manager="transactionManager"/>,可以使用@EnableTransactionManagement注解代替,
bean的扫描<context:component-scan base-package="com.shh.jdbcTemplate"/>,可以用@ComponentScan代替,所以最终全注解配置类如下:

@Configuration
@ComponentScan("com.shh.jdbcTemplate")
@Import({JdbcConfig.class, TransactionConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}

修改我们的测试代码,加载SpringConfig

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class JdbcTemplateTest {
    @Autowired
    private IAccountService accountService;

    @Test
    public void transfer() {
        accountService.transfer("张三", "李四", 100);
    }
}

至此,全注解的Spring 声明式事务控制改造完毕。

这三种事务配置方式,前两种只需在xml中按照约定的步骤配置即可,而基于注解配置相对简单些,但需要在每个需要事务控制的service层业务类上添加@Transactional注解,如何选择就要看实际的需求了,我个人更倾向于前两种xml配置方式。

相关文章

  • 116、【JavaEE】【Spring】Spring 事务

    1、Spring 事务控制方式 Spring 的事务控制可以分为编程式事务控制和声明式事务控制。 编程式,即编写有...

  • 手写系列之 —— 实现Spring事务注解功能

    Spring事务分为编程式事务和声明式事务,编程式事务是手动控制,声明式事务是利用注解或者配置文件自动实现事务控制...

  • Spring事务

    基础概念 ​ Spring中事务支持编程式事务和声明式事务。编程式事务由使用者自行编码控制事务;声明式事务则是...

  • spring04

    Spring JdbcTemplate学习 Spring 声明式事务 xml配置实现 Spring 声明式事务 注...

  • 声明式事务

    编程式事务:由程序员编程事务控制代码声明式事务:事务控制代码已由Spring写好,程序员只需声明出哪些方法需要进行...

  • Spring 声明式事务控制

    Spring 的事务控制可以分为编程式和声明式两种,编程式需要开发者通过编写代码的方式来实现事务管理,并不好用。而...

  • Spring的事务机制解析一

    一Spring事务的种类 1.声明式事务 2.编程式事务 二Spring事务的具体描述 (一)声明式事务 1.声明...

  • 手写源码(一):自己实现Spring事务

    手写Spring事务 Spring事务分为声明式事务(注解或包扫描)和编程式(在代码里提交或回滚)事务,声明式事务...

  • spring事务(二) 声明式事务

    spring事务(二) 声明式事务 知识导读 声明式事务是对编程式事务的包装 声明式事务通过使用AOP来实现,注册...

  • Java Spring-声明式事务

    Spring-声明式事务

网友评论

      本文标题:Spring 声明式事务控制

      本文链接:https://www.haomeiwen.com/subject/asebactx.html