spring基于注解开发实现自定义的事务支持
1、定义一个管理连接的类ConnectionUtil,实现事务的核心思想就是使用ThreadLocal对象把Connection对象和当前线程绑定,从而使一个线程中只有一个能控制事务的对象。(事务是由Connection进行控制的)
package com.wy.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
@Component
public class ConnectionUtil {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
/**
* 获取当前线程上的连接(实现当前线程和Conection的绑定)
*
* @return
*/
public Connection getThreadConn() {
try {
//1、从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//1、从ThreadLocal上获取
conn = dataSource.getConnection();
//2、将连接绑定到当前线程上
tl.set(conn);
}
return conn;
} catch (Exception e) {
throw new RuntimeException();
}
}
/**
* 将线程和数据库的连接解绑
*/
public void removeConn(){
tl.remove();
}
}
2、定义一个事务管理器,用于管理Connection的事务操作(该Connection是与当前线程绑定的connection)
package com.wy.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
/**
* 事务管理器
*/
@Component("tx")
public class TransactionManager {
@Autowired
private ConnectionUtil connectionUtil;
/**
* 开启事务
*/
public void beginTransaction() throws SQLException {
connectionUtil.getThreadConn().setAutoCommit(false);
}
/**
* 提交事务
*/
public void commitTransaction() throws SQLException {
connectionUtil.getThreadConn().commit();
}
/**
* 回滚事务
*/
public void rollbackTransaction() throws SQLException {
connectionUtil.getThreadConn().rollback();
}
/**
* 关闭事务
* 细节:由于使用了数据库连接池,其实质就是将池中的线程与数据库的连接进行绑定
* ***所以使用关闭事务的时候需要将线程和连接解绑
*/
public void releaseTransaction() throws SQLException {
//关闭连接
connectionUtil.getThreadConn().close();
//线程和连接解绑
connectionUtil.removeConn();
}
}
3、逻辑代码的实现
3.1配置类,用于spring在容器中创建数据源和queryRunner
package com.wy.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.SQLException;
@Configuration
@ComponentScan({"com.wy"})
@PropertySource({"classpath:JdbcConfig.properties"})
public class AppConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.user}")
private String user;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(user);
dataSource.setPassword(password);
return dataSource;
}
/**
* 当我不向queryRunner传入dataSource时,是支持事务的
* 而传入时则事务失败
* @param
* @return
*/
@Bean
public QueryRunner getRunner() {
return new QueryRunner();
}
/**
* 无论是否为QueryRunner注入数据源都可以,但是在实现CRUD的时候必须使用QunerryRuner带
* Connection的方法,保证一个线程只能绑定一个connection,否则事务失败
*
*/
// @Bean
// public QueryRunner getRunner(DataSource dataSource) {
// System.out.println("config >>>>"+dataSource.hashCode());
// return new QueryRunner(dataSource);
// }
}
3.2 Dao的实现类
package com.wy.dao;
import com.wy.bean.Account;
import com.wy.util.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
import java.util.List;
@Repository
public class AccountDao {
@Autowired
private QueryRunner queryRunner;
@Autowired
ConnectionUtil connectionUtil;
/**
* 查所有
*
* @return
* @throws SQLException
*/
public List<Account> findAll() throws SQLException {
//注意:一定要使用ConnectionUtil中与线程绑定的Connection
return queryRunner.query(connectionUtil.getThreadConn(),"select * from account",
new BeanListHandler<Account>(Account.class));
}
/**
* 根据id查
*
* @param id
* @return
*/
public Account findById(int id) throws SQLException {
return queryRunner.query(connectionUtil.getThreadConn(),"select * from account where id = ?",
new BeanHandler<Account>(Account.class), id);
}
/**
* 保存
*
* @param account
* @return
*/
public int save(Account account) throws SQLException {
return queryRunner.update(connectionUtil.getThreadConn(),"insert into account(accountname,money) values(?,?)",
account.getAccountName(), account.getMoney());
}
/**
* 修改
*
* @param account
* @return
*/
public int update(Account account) throws SQLException {
return queryRunner.update(connectionUtil.getThreadConn(),"update account set accountname = ?,money = ? where id = ?",
account.getAccountName(), account.getMoney(), account.getId());
}
/**
* 删除
* @param id
* @return
*/
public int delete(int id) throws SQLException {
return queryRunner.update(connectionUtil.getThreadConn(),"delete from account where id = ?", id);
}
}
3.3 Service的实现类,加入了事务的控制
package com.wy.service;
import com.wy.bean.Account;
import com.wy.dao.AccountDao;
import com.wy.util.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.sql.SQLException;
import java.util.List;
/**
* 保证事务原子性操作的前提条件是:只有一个connection对象实现所有的CRUD工作。
*
* 所以,保证了一个操作中connection的单例就能保证事务的原子性
* 可以使用ThreadLocal 对象把connection对象和当前线程绑定
*/
@Service
public class AccountService {
@Autowired
@Qualifier("tx")
TransactionManager txManager;
@Autowired
AccountDao accountDao;
public int save(Account account) throws SQLException {
try{
//1、开启事务
txManager.beginTransaction();
//2、执行操作
int save = accountDao.save(account);
//3、提交事务
txManager.commitTransaction();
//4、返回结果
return save;
}catch (Exception e){
//5、事务回滚
txManager.rollbackTransaction();
throw new RuntimeException();
}finally {
//6、释放资源
txManager.releaseTransaction();
}
}
public int update(Account account) throws SQLException {
try{
//1、开启事务
txManager.beginTransaction();
//2、执行操作
int update = accountDao.update(account);
//添加异常测试是否能够控制事务
int i = 1/0;
//3、提交事务
txManager.commitTransaction();
//4、返回结果
return update;
}catch (Exception e){
//5、事务回滚
txManager.rollbackTransaction();
throw new RuntimeException();
}finally {
//6、释放资源
txManager.releaseTransaction();
}
}
public int delete(int id) throws SQLException {
try{
//1、开启事务
txManager.beginTransaction();
//2、执行操作
int update = accountDao.delete(id);
//3、提交事务
txManager.commitTransaction();
//4、返回结果
return update;
}catch (Exception e){
//5、事务回滚
txManager.rollbackTransaction();
throw new RuntimeException();
}finally {
//6、释放资源
txManager.releaseTransaction();
}
}
public List<Account>findAll() throws SQLException {
try{
//1、开启事务
txManager.beginTransaction();
List<Account> ls = accountDao.findAll();
// int i = 1/0;
//3、提交事务
txManager.commitTransaction();
//4、返回结果
return ls;
}catch (Exception e){
//5、事务回滚
txManager.rollbackTransaction();
throw new RuntimeException();
}finally {
//6、释放资源
txManager.releaseTransaction();
}
}
public Account findById(int id) throws SQLException {
try{
Account account = accountDao.findById(id);
//3、提交事务
txManager.commitTransaction();
//4、返回结果
return account;
}catch (Exception e){
//5、事务回滚
txManager.rollbackTransaction();
throw new RuntimeException();
}finally {
//6、释放资源
txManager.releaseTransaction();
}
}
}
3.4 测试类
package com.wy;
import com.wy.bean.Account;
import com.wy.config.AppConfiguration;
import com.wy.service.AccountService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.sql.SQLException;
import java.util.Iterator;
public class AccountTest {
AnnotationConfigApplicationContext ctx;
AccountService accountService;
//初始化spring容器
@Before
public void before(){
ctx = new AnnotationConfigApplicationContext(AppConfiguration.class);
accountService = ctx.getBean(AccountService.class);
}
@Test
public void fun1() throws SQLException {
Iterator<Account> iterator = accountService.findAll().iterator();
while(iterator.hasNext())
System.out.println(iterator.next());
}
@Test
public void fun2() throws SQLException {
Account account = new Account(3,"王二麻子", 0);
accountService.update(account);
}
}