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);
}
}