首页 > Java > DBUtils(数据库操作框架)
2014
08-26

DBUtils(数据库操作框架)

4、实际开发中如何处理事务(☆ AOP编程)

本文还是以实际生活中的转账为例讲解实际开发中事务的处理,使用数据库如下:

create table account(
id int primary key auto_increment,
name varchar(100),
balance float
);
insert into account values(1,’a',1000);
insert into account values(2,’b',1000);

1)第一版:直接在业务层实现中处理事务

首先必须明确一点,控制事务是业务层的事,而非DAO层,业务层的实现如下:

public class BusinessServiceImpl implements BusinessService {
	public void transfer(String sourceAccountName, String targetAccontName,
			float money) {
		Connection conn = null;
		try {
			//控制事务由Service层负责
			conn = DBCPUtil.getConnection();
			conn.setAutoCommit(false);
			AccountDao dao = new AccountDaoImpl(conn);
			//查询A账户、B账户,并修改余额
			Account sAccount = dao.findByName(sourceAccountName);
			Account tAccount = dao.findByName(targetAccontName);
			sAccount.setMoney(sAccount.getMoney() - money);
			tAccount.setMoney(tAccount.getMoney() + money);
			//执行更新
			dao.updateAcount(sAccount);
			// int i=1/0; //模拟转账过程中发生异常
			dao.updateAcount(tAccount);
		} catch (Exception e) {
			if (conn != null) {
				try {
					conn.rollback();
				} catch (SQLException e1) {
					e1.printStackTrace();
				}
			}
			e.printStackTrace();
		} finally {
			if (conn != null) {
				try {
					conn.commit();
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

DAO层的实现如下:

public class AccountDaoImpl implements AccountDao {
	//需使用QueryRunner里面支持事务的那组API
	private QueryRunner qr = new QueryRunner();
	private Connection conn;
	//Service层调用DAO层的时候传入Connection对象
	public AccountDaoImpl(Connection conn){
		this.conn = conn;
	}

	public Account findByName(String sourceName) {
		try {
			return qr.query(conn, "select * from account where name = ?", new BeanHandler(Account.class),sourceName);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	public void update(Account sAccount) {
		try {
			qr.update(conn, "update account set balance=? where name = ?",sAccount.getBalance(),sAccount.getName());
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
}

2)第二版:利用ThreadLocal管理事务(增加TransactionManager类)

上面的版本中,由于需要在业务层考虑事务,因此增加了业务层的编码复杂度,并且在业务层出现了Connection对象(本应该只在DAO层出现),本节抽取了事务管理的公共类:TransactionManager类,降低了业务层的编写难度。

补充:ThreadLocal类(线程局部变量)
// 特点:一个线程存的东西,只有该线程才能取出来(☆)。
// 模拟ThreadLocal内部实现(理解的关键)
public class ThreadLocal{
//类似Map的结构
private Map<Runnable,Object> map = new HashMap<Runnable,Object>();
public void set(Object obj){
map.put(Thread.currentThread(),obj);
}
public void remove(){
map.remove(Thread.currentThread());
}
public Object get(){
return map.get(Thread.currentThread());
}
}
有关ThreadLocal类,在《线程安全问题》一节也有提到。

① TransactionManager类:封装了事务管理共同的部分,任何用到事务的地方都可以调用该类中提供的方法。

public class TransactionManager {
	//利用ThreadLocal存储用户线程对应的连接
	private static ThreadLocal tl = new ThreadLocal();

	public static Connection getConnection(){
		Connection conn = tl.get();
		if(conn == null){
		//如果tl中不存在该用户线程对应的连接,则创建连接并放入tl中
			conn = DBCPUtil.getConnection();
			tl.set(conn);
		}
		return conn;
	}

	public static void startTransaction(){
		try {
			Connection conn = getConnection();
			conn.setAutoCommit(false);//开启事务
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void rollback(){
		try {
			Connection conn = getConnection();
			conn.rollback();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void commit(){
		try {
			Connection conn = getConnection();
			conn.commit();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public static void release(){
		try {
			Connection conn = getConnection();
			conn.close();
			tl.remove();//必须要将连接同当前线程解绑。这 与服务器实现有关:服务器采用线程池 ☆。
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

② 改写业务层和DAO层的实现

public class AccountServiceImpl implements AccountService {
	private AccountDao dao = new AccountDaoImpl();

	public void transfer(String sourceName, String targetName, float money) {
		try {
			TransactionManager.startTransaction();//开启事务

			Account sAccount = dao.findByName(sourceName);
			Account tAccount = dao.findByName(targetName);

			sAccount.setBalance(sAccount.getBalance() - money);
			tAccount.setBalance(tAccount.getBalance() + money);

			dao.update(sAccount);
			//int i = 1/0;
			dao.update(tAccount);
		} catch (Exception e) {
			TransactionManager.rollback();//回滚事务
			e.printStackTrace();
		}finally{
			TransactionManager.commit();//提交事务
			TransactionManager.release();
		}
	}
}
------------↑业务层实现↑---------↓DAO层实现↓----------------
public class AccountDaoImpl implements AccountDao {
	//需使用QueryRunner里面支持事务的那组API
	private QueryRunner qr = new QueryRunner();

	public Account findByName(String sourceName) {
		try {
			return qr.query(TransactionManager.getConnection(), "select * from account where name = ?", new BeanHandler(Account.class),sourceName);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	public void update(Account sAccount) {
		try {
			qr.update(TransactionManager.getConnection(), "update account set balance=? where name = ?",sAccount.getBalance(),sAccount.getName());
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
}

3)第三版:AOP编程

关于AOP的知识详见《动态代理技术》一文。事务、安全、日志等都可以看成系统的交叉业务,AOP的目标就是要使交叉业务模块化。

首先,将事务模块化,即TransactionManager类,代码同上。

其次,改写系统的业务代码和DAO层代码:

public class AccountServiceImpl implements AccountService {
	private AccountDao dao = new AccountDaoImpl();

	public void transfer(String sourceName, String targetName, float money) {
		Account sAccount = dao.findByName(sourceName);
		Account tAccount = dao.findByName(targetName);

		sAccount.setBalance(sAccount.getBalance() - money);
		tAccount.setBalance(tAccount.getBalance() + money);

		dao.update(sAccount);
		// int i = 1/0; //模拟转账过程中的异常
		dao.update(tAccount);
	}
}

DAO层的代码同第二版,可以看出业务层已经没有任务事务控制的代码了,只处理具体的业务逻辑。

最后,AccountServiceFactory类,生成AccountServiceImpl的代理对象,并加入切面代码:

public class AccountServiceFactory {
	public static AccountService getAccountService() {
		final AccountService service = new AccountServiceImpl();
		// 生成service的代理实例并返回
		AccountService serviceProxy = (AccountService) Proxy.newProxyInstance(
				service.getClass().getClassLoader(), service.getClass()
						.getInterfaces(), new InvocationHandler() {
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						try {
							TransactionManager.startTransaction();// 开启事务
							return method.invoke(service, args);
						} catch (Exception e) {
							TransactionManager.rollback();
							throw new RuntimeException(e);
						} finally {
							TransactionManager.commit();// 提交事务
							TransactionManager.release();//
						}
					}
				});
		return serviceProxy;
	}
}

至此,完成了对事务的AOP改写,此时,在表现层(用户层)获取业务层的AccountService对象时,不是直接去new一个AccountServiceImpl,而是通过AccountServiceFactory.getAccountService()获得。测试代码如下:

@Test
public void test() {
	// 获取AccountService,不能用new AccountServiceImpl();了
	AccountService service = AccountServiceFactory.getAccountService();
	service.transfer("b", "a", 100);
}

留下一个回复

你的email不会被公开。