Ubtils中事务处理

dbutils中处理事务,其连接池需要手动获取和手动关闭,因此需要使用QurryRunner类中的默认构造函数。使用带Connection参数的query和update,或者batch方法来操作sql语句。使用带参数的QurryRunner的构造函数,没执行一条sql语句,都将自动获取连接和产生和关闭preparestament 和resultset。

下面是事物处理的一个小示例:

//向account数据表中插入一条记录,同时修改此条记录,最后删除这条记录,这些sql语句都是在一个事务中处理的,同时在此事务中,提供了两个回滚点。要是此事物出现异常,将回滚到事务起始状态。conn.setAutoCommit(false),设置执行一条语句后不会自动提交,即不让事务自动关闭。此语句相当于开启一个事务。而conn.commit()则是关闭一个事务。

@Test
	public void tranactionTest() {
		Connection conn = null;
		try {
			conn = JdbcC3p0Pools.getConnection2();
			conn.setAutoCommit(false);
			QueryRunner qr = new QueryRunner();
			
			String sql = "insert into account(name, money) values(?,?)";
			Object[] param= {"jack", 1000};
			qr.update(conn, sql, param);
			Savepoint sp = conn.setSavepoint();
			
			sql = "update account set money=money-100 where name=?";
			Object param2 = "jack";
			qr.update(conn, sql, param2);
			Savepoint sp2 = conn.setSavepoint();
			
			sql = "delete from account where name=?";
			qr.update(conn, sql, param2);
			conn.rollback(sp2);
			conn.commit();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			try {
				conn.rollback();
			} catch (SQLException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}finally{
			if(conn!=null){
				try {
					conn.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}
//上面执行结果将是插入一条jack记录,同时将其money修改为900

| 55 | jack    |   900 |

上面的处理显得很粗糙,根据MVC三层架构设计,下面模拟了一个转账事务。

//code

public class AccountDaoImpl {
	Connection conn = null;
	
	public AccountDaoImpl(Connection conn){
		this.conn = conn;
	}
	
	public void update(Account account) throws SQLException{
		QueryRunner qr = new QueryRunner();
		String sql = "update account set name=?, money=? where id=?";
		Object[] param = {account.getName(), account.getMoney(), account.getId()};
		qr.update(conn, sql, param);
	}
	
	public Account find(int id) throws SQLException{
		QueryRunner qr = new QueryRunner();
		String sql = "select * from account where id=?";
		Object[] param = {id};
		return (Account) qr.query(conn, sql, new BeanHandler(Account.class), param);
	}
}

package cn.itcast.service.impl;

import java.sql.Connection;
import java.sql.SQLException;

import cn.itcast.dao.impl.AccountDaoImpl;
import cn.itcast.domain.Account;
import cn.itcast.service.AccountService;
import cn.itcast.utils.JdbcC3p0Pools;

public class AccountServiceImpl implements AccountService {
	private Connection conn = null;

	/* (non-Javadoc)
	 * @see cn.itcast.service.impl.AccountService#tranfer(int, int, float)
	 */
	public void tranfer(int sourceId, int toDestId, float money) {
		try {
			conn = JdbcC3p0Pools.getConnection2();
			conn.setAutoCommit(false);
			AccountDaoImpl dao = new AccountDaoImpl(conn);
			Account A = dao.find(sourceId);
			Account B = dao.find(toDestId);

			A.setMoney(A.getMoney() - money);
			B.setMoney(B.getMoney() + money);
			dao.update(A);
			dao.update(B);
			conn.commit();

		} catch (Exception e) {
			conn.rollback();
			throw new RuntimeException(e);
		} finally {
			JdbcC3p0Pools.close(conn);
		}
	}
}

package cn.itcast.demo;

import cn.itcast.service.AccountService;
import cn.itcast.service.impl.AccountServiceImpl;

public class AccountTransferDemo {
	public static void  main(String[] args){
		int sourceId = 54;
		int destId = 55;
		float money = 80;
		
		AccountService service = new AccountServiceImpl();
		service.tranfer(sourceId, destId, money);
	}
}

//转账前
| 54 | kebi    |   820 |
| 55 | jack    |   900 |
转账后
| 54 | kebi    |   740 |
| 55 | jack    |   980 |
按照MVC三层架构来设计,降低了各层间的耦合性,但是上面这中实现方法,在实际应用中依然欠妥。在实际应用中,这段转账代码会在并发操作中的多个线程中执行,因此此connection会被多个线程拥有,为了防止对共享变量访问的冲突,可以使用同步操作,使在某一时刻只有一个线程访问connection,待此访问完后,再允许下一个线程对其访问,这就是以时间换空间的做法。另外还有一种做法就是以空间换时间的做法,这种方法可以使用ThreadLocal,这个其实是一个容器。可以多线程共享的变量,此处为connection为每个线程申请一个副本,将这个副本绑定到每个线程的ThreadLocal容器对象上,这样可以将每个线程对此变量的操作进行隔离,避免产生冲突。
在使用ThreadLocal来优化上面的转账代码前,这里先对ThreadLocal的接口方法进行说明:
ThreadLocal类接口很简单,只有4个方法:
void set(Object value)
设置当前线程的线程局部变量的值;
public Object get()
该方法返回当前线程所对应的线程局部变量;
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度,建议调用此操作,例如:在WEB应用中,当多个客户端都同时访问时,每个客户端都会绑定一个这么一个局部变量,很容易导致服务器内存崩溃;
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null.
ThreadLocal支持泛型操作ThreadLocal<T>,还需要说明一点的是,ThreadLocal的set方法,会将局部变量存储到一个hashMap中去,key值为此线程标识号,value为此局部变量。
//使用ThreadLocal来优化转账操作。

//工具类:

package cn.itcast.utils;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcDbutils {
	private static ThreadLocal<Connection> localThread = new ThreadLocal();
	private static DataSource ds = null;
	private static Connection conn = null;
	
	static{
		ds = new ComboPooledDataSource("mysql");
	}
	
	public static void startTransaction(){
		conn =  localThread.get();
		try {
			if(conn ==null){		
				conn = ds.getConnection();
				localThread.set(conn);
			}
			conn.setAutoCommit(false);
		}catch (SQLException e) {
		// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}	
	}
	
	public static void commit(){
		conn =  localThread.get();
		if(conn !=null){
			try {
				conn.commit();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
			localThread.remove();
		}
	}
	
	public static void release(){
		if(conn!=null){
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}
	
	public static void rollback(){
		if(conn!=null){
			try {
				conn.rollback();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
	}
	
	public static Connection getConnection(){
		return localThread.get();
	}
	
}

//dao类

package cn.itcast.dao.impl;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import cn.itcast.dao.AccountDao;
import cn.itcast.domain.Account;
import cn.itcast.utils.JdbcDbutils;

public class AccountDaoImpl implements AccountDao {
	private Connection conn = null;
	public AccountDaoImpl(Connection conn){
		this.conn = conn;
	}
	
	public AccountDaoImpl(){
		
	}
	
	public void update(Account account) throws SQLException{
		QueryRunner qr = new QueryRunner();
		String sql = "update account set name=?, money=? where id=?";
		Object[] params={account.getName(), account.getMoney(), account.getId()};
//		qr.update(conn, sql, params);
		qr.update(JdbcDbutils.getConnection(), sql, params);
	}
	
	public Account find(int id) throws SQLException{
		QueryRunner qr = new QueryRunner();
		String sql = "select * from account where id=?";
		Object[] params={id};
		return (Account) qr.query(JdbcDbutils.getConnection(), sql, new BeanHandler(Account.class), params);
	}
}

//service层

public void transfer(int sourceId, int toDestId, float money) {
		try {
			JdbcDbutils.startTransaction();
			AccountDao dao = new AccountDaoImpl();
			Account A;		
			A = dao.find(sourceId);
			
			Account B = dao.find(toDestId);		
			A.setMoney(A.getMoney()-money);
			dao.update(A);
			B.setMoney(B.getMoney()+money);
			dao.update(B);
			JdbcDbutils.commit();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			JdbcDbutils.rollback();
		}finally{
			JdbcDbutils.release();
		}
	}
//测试程序

package cn.itcast.demo;

import cn.itcast.service.AccountService;
import cn.itcast.service.impl.AccountServiceImpl;

public class TransferAccountDemo {
	public static void main(String[] args){
		int idA = 13;
		int idB = 12;
		float money = 80;
		
		AccountService service = new AccountServiceImpl();
		service.threadLocalTransfer(idA, idB, money);
		
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值