Spring 声明式事务控制

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.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配置



    
    

    
    

    
    
        
    

    
    
        
        
        
        
    

    
    
        
    

    
    
    
        
        
        
        
        
        
        
        
            
            
            
            
            
        
    
    
    
        
        
        
        
    
    

比较重要的点都在注释中有写到,至于事务是如何开启、提交、回滚等操作都是由 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;放开,模拟转账失败,转账时会抛出异常,事务回滚,两个账户的余额不会变化:

Spring 声明式事务控制_第1张图片


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

经测试已经实现了我们的目的,接下来看第二种xml配置方式:

三、使用拦截器类的xml配置




    

    
    

    
    
        
    

    
    
        
        
        
        
    

    
    
        
    

    
    
    
        
        
        
            
                PROPAGATION_REQUIRED
                PROPAGATION_SUPPORTS,readOnly
                PROPAGATION_SUPPORTS,readOnly
                PROPAGATION_SUPPORTS,readOnly
                PROPAGATION_SUPPORTS,readOnly
            
        
    
    
    
        
            
                
                *Service
            
        
        
            
                transactionInterceptor
            
        
    
    

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

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

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

四、使用xml+注解的配置

这里 xml 的主要功能是开启Spring对基于注解事务的支持,就是最后那行配置:




    

    
    

    
    
        
    

    
    
        
        
        
        
    

    
    
        
    

    
    
    
    
    

然后就是在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对基于注解事务的支持,可以使用@EnableTransactionManagement注解代替,
bean的扫描,可以用@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配置方式。

你可能感兴趣的:(Spring 声明式事务控制)