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 完成的,目前需要我们关心的是配置事务控制的主要步骤:
- 配置事务管理器
- 配置事务的通知,这里需要用到事务管理器,然就是配置事务的属性,哪些方法会被匹配到,可以使用事务管理
- 使用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配置
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
的加载、jdbcTemplate
、dataSource
bean对象的管理,可以用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配置方式。