整合spring事务控制的三种方式
- 手动控制事务
- 使用切面aop控制
- 声明式事务控制
a) 三种方式当属手动模式较原始且麻烦,未经框架整合,但是最能凸显底层原理所在,作为了解底层结构有帮助
b) 切面是spring的特色了,可以做实现外加业务而无需对源码做更改,让人很省心,当然优秀的框架自然更加整合度更高,不至于让人从底层逻辑开始开发,而忽略业务逻辑,对于事务控制还需要整合更多.
c) 声明式事务控制可以使用jdbc模板,虽然这个模板无法实现大规模业务爆发,但是实现小的需求还是很方便的,通过spring整合mybatis框架会更加优秀.
d) 介绍中决定使用部分注解和部分配置文件结合,方便也现实些,毕竟即不建议全程xml配置狂撸,硬伤,也不建议全程注解裸奔,这么多我也记不过来!
e) 还有一种就是编程式事务控制,其类似于声明式事务控制,又比声明式事务控制麻烦,不实用,就不介绍了!
大概业务逻辑:
- 创建工程(略)
- 导入依赖(lue)
- 容器(配置文件)
- 日志配置文件和jdbc配置文件(略)
- 原始数据(略)
- 数据封装类(略)
- 数据持久层—整合DBUtils框架(或者使用Spring提供的JdbcTemplate模板)
- 业务层
- (手动实现事务控制还需要创建连接和事务管理)
- 测试—整合junit框架
------------------------------------------------------华丽的分割线------------------------------------------------------------------------------------
第一种方式:手动控制事务
1.工程框架
2.容器
<!--开启注解扫描 -->
<context:component-scan base-package="java"></context:component-scan>
<!--导入数据库配置文件-->
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<!--导入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--Bean注入,这里使用注解更方便-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="dao.impl.AccountDaoImpl"></bean>
<bean id="connectionUtils" class="utils.ConnectionUtils"></bean>
<bean id="transactionManager" class="utils.TransactionManager"></bean>
3.dao层
public class AccountDaoImpl implements AccountDao {
@Autowired
//注入连接,绑定连接和线程,为了让每次进行CRUD都使用同一个连接
ConnectionUtils connectionUtils;
@Autowired
QueryRunner queryRunner;
public Account findByName(String name) {
List<Account> list = null;
try {
list = queryRunner.query(connectionUtils.getConnection(),"select * from account where name = ?", new BeanListHandler<Account>(Account.class), name);
if (list.isEmpty()){
return null;
}else if (list.size()>1){
throw new RuntimeException("账户不唯一,无法确定");
}
} catch (SQLException e) {
e.printStackTrace();
}
return list.get(0);
}
public void update(Account account) {
try {
queryRunner.update(connectionUtils.getConnection(),"update account set name = ?,money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4.service层
public class AccountServiceImpl implements AccountService {
@Autowired
//导入事务管理器
TransactionManager transactionManager;
@Autowired
AccountDao accountDao;
public Account findByName(String name) {
return accountDao.findByName(name);
}
public void update(Account account) {
accountDao.update(account);
}
public void updateAccount(String name1, String name2, Float money) {
try {
//开启事务
transactionManager.begin();
Account account1 = accountDao.findByName(name1);
Account account2 = accountDao.findByName(name2);
Float money1 = account1.getMoney();
if (money1 >= 100f){
account1.setMoney(account1.getMoney()-money);
}else {
throw new RuntimeException("账户余额不足,转账不成功");
}
account2.setMoney(account2.getMoney()+money);
accountDao.update(account1);
//在中间加入异常,测试事务管理是否成功
int i= 1/0;
accountDao.update(account2);
//提交事务
transactionManager.commit();
} catch (RuntimeException e) {
//回滚事务
transactionManager.rollback();
e.printStackTrace();
} finally {
//关闭事务
transactionManager.close();
}
}
}
5.自定义连接
public class ConnectionUtils {
ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
//注入数据源
DataSource dataSource;
public Connection getConnection(){
//从本地容器中获取连接
Connection conn = tl.get();
if (conn==null){
try {
//如果没有连接就从数据源中获取
conn = dataSource.getConnection();
tl.set(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn;
}
//释放连接
public void release(){
tl.remove();
}
}
6.自定义事务管理器
public class TransactionManager {
@Autowired
ConnectionUtils connectionUtils;
//手动开启事务,把自动提交改为手动提交
public void begin(){
try {
connectionUtils.getConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//事务提交方法
public void commit(){
try {
connectionUtils.getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//事务回滚方法
public void rollback(){
try {
connectionUtils.getConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//事务关闭和释放连接方法
public void close(){
try {
connectionUtils.getConnection().close();
connectionUtils.release();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
7.测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:ApplicationContext.xml")
public class AccountTest {
@Autowired
AccountService accountService;
@Test
public void function(){
accountService.updateAccount("bbb","aaa",100f);
}
}
--------------------------------------------------------------华丽的分割线----------------------------------------------------------------------------
第二种方式:AOP切面
切面的方式相比较手动控制事务,无需更改业务层代码,减少代码侵入
切面的方式需要明确切面,切入点和通知等
这里我只写更改后的代码吧,免得太冗余
1.容器:添加aop相关配置
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--设置aop配置-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pointcut" expression="execution(* service..*.*(..))"></aop:pointcut>
<!--配置切面(这里事务管理器就是切面)和通知-->
<aop:aspect id="manager" ref="transactionManager">
<aop:around method="around" pointcut-ref="pointcut"></aop:around>
</aop:aspect>
</aop:config>
2.service层把之前的事务控制代码去掉,还原成原始代码(只拿修改过的这个方法)
//转账方法
public void updateAccount(String name1, String name2, Float money) {
Account account1 = accountDao.findByName(name1);
Account account2 = accountDao.findByName(name2);
Float money1 = account1.getMoney();
if (money1 >= 100f){
account1.setMoney(account1.getMoney()-money);
}else {
throw new RuntimeException("账户余额不足,转账不成功");
}
account2.setMoney(account2.getMoney()+money);
accountDao.update(account1);
//在中间加入异常,测试事务管理是否成功
//int i= 1/0;
accountDao.update(account2);
}
3.事务管理器:只添加了一个环绕通知,其余不变
创建环绕通知,在使用注解的方式的前提下建议选择环绕通知,因为可以控制通知的执行顺序,避免关闭事务在提交事务之前就执行,造成事务无法回滚
public class TransactionManager {
@Autowired
ConnectionUtils connectionUtils;
//环绕通知
public Object around(ProceedingJoinPoint pjp){
Object obj = null;
try {
//开启事务
this.begin();
obj = pjp.proceed(pjp.getArgs());
//提交事务
this.commit();
} catch (Throwable throwable) {
//回滚事务
this.rollback();
throwable.printStackTrace();
}finally {
//关闭事务
this.close();
}
return obj;
}
--------------------------------------------------------华丽的分割线----------------------------------------------------------------------------------
第三种方式:声明式事务控制
声明式事务控制相对比aop又更加方便一些,不再需要自己创建连接和事务管理器,如果使用注解就更加简洁明了了,为了方便这里dao层就使用jdbc模板了
1.容器:需要添加事务管理和事务控制等,如果不使用注解还可以选择aop的方式
<!--开启注解扫描 -->
<context:component-scan base-package="java"></context:component-scan>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--导入数据库配置文件-->
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<!--数据源不再使用c3p0,更换为模板-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--事务配置,基于注解,也可以使用aop,只是aop配置相对麻烦-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
<!--Bean注入-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="dao.impl.AccountDaoImpl"></bean>
2.dao层:使用jdbc模板
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
//导入数据源--通过set方法的形参
@Autowired
public void setDs(DataSource dataSource){
super.setDataSource(dataSource);
}
public Account findByName(String name) {
List<Account> list = super.getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
if (list.isEmpty()){
return null;
}else if (list.size()>1){
throw new RuntimeException("账户不唯一,无法确定");
}
return list.get(0);
}
public void update(Account account) {
super.getJdbcTemplate().update("update account set name = ?,money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
}
}
3.service层:业务层只是多添加了一行注解即可
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,readOnly = false)
public class AccountServiceImpl implements AccountService {
@Autowired
AccountDao accountDao;
public Account findByName(String name) {
return accountDao.findByName(name);
}
public void update(Account account) {
accountDao.update(account);
}
public void updateAccount(String name1, String name2, Float money) {
Account account1 = accountDao.findByName(name1);
Account account2 = accountDao.findByName(name2);
Float money1 = account1.getMoney();
if (money1 >= 100f){
account1.setMoney(account1.getMoney()-money);
}else {
throw new RuntimeException("账户余额不足,转账不成功");
}
account2.setMoney(account2.getMoney()+money);
accountDao.update(account1);
//在中间加入异常,测试事务管理是否成功
//int i= 1/0;
accountDao.update(account2);
}
}
4.测试还是一样,就不写了