jdbc的事物管理与ThreadLocale

来源:互联网 发布:php 创建二维码ticket 编辑:程序博客网 时间:2024/06/06 00:41
模拟场景 银行从aa转化转账到bb账户 要求要么同时成功要么同时失败 此时就要使用到了事务处理, 准备好account 表 和数据库连接池我使用的是c3p0做数据连接池(需要配置文件),操作数据库使用Apache—DBUtils框架为了减少代码量commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate的公司的首选。

 

 CREATE TABLE `account` (  `id` int(11) NOT NULL auto_increment,  `name` varchar(40) default NULL,  `money` float default NULL,  PRIMARY KEY  (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8

数据连接池代码:

public class JdbcUtils{private static ComboPooledDataSource ds;static{ds = new ComboPooledDataSource();}public static DataSource getDataSource(){return ds;}public static Connection getConnection() throws Exception{return ds.getConnection();}}

 

在没有涉及事务的时候 从aa 转到bb的时候是dao这样子实现的:

public class AccountDao{public Account find(int id){try{QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());String sql = "select * from account where id=?";return (Account) qr.query(JdbcUtils.getConnection(),sql,id, new BeanHandler(Account.class));}catch (Exception e) {throw new RuntimeException(e);}}}public void update(Account a){try{QueryRunner qr=new QueryRunner(JdbcUtils.getDataSource());String sql="update account set money=? where id=?";Object []params={a.getMoney(),a.getId()};qr.update(JdbcUtils.getConnection(),sql,params);} catch (Exception e){throw new RuntimeException(e);}}


在执行service的时候 是2个不同的connction 根本无法执行事务:

AccountDao dao = new AccountDao();Account a = dao.find(sourceid);Account b = dao.find(targetid);a.setMoney(a.getMoney()-money);b.setMoney(b.getMoney()+money);dao.update(a);  //sqldao.update(b);  //sql


 

于是就这样子解决,但是dao只负责实现增删改查的功能具体实现交个servic去实现

public void transfer(){QueryRunner qr = new QueryRunner();Connection conn = null;try{conn = JdbcUtils.getConnection();conn.setAutoCommit(false);String sql1 = "update account set money=money-100 where name='aaa'";qr.update(conn, sql1);String sql2 = "update account set money=money+100 where name='bbb'";qr.update(conn, sql2);conn.commit();} catch (Exception e){try{conn.rollback();conn.commit();} catch (Exception e2){}throw new RuntimeException(e);} finally{try{conn.close();} catch (SQLException e){e.printStackTrace();}}}


此时为了统一connection就必须指定connection 在构造方法中添加了connection 使用dao 的时候就可以避免connection 的不统一

public class AccountDao{private Connection conn;public AccountDao(){}public AccountDao(Connection conn){this.conn=conn;}public Account find(int id){try{QueryRunner qr=new QueryRunner();String sql="select * from account where id=?";return (Account)qr.query(conn, sql,id, new BeanHandler(Account.class));} catch (Exception e){throw new RuntimeException(e);}}public void update(Account a){try{QueryRunner qr = new QueryRunner();String sql = "update account set money=? where id=?";Object[] params ={ a.getMoney(), a.getId() };qr.update(conn, sql, params);} catch (Exception e){throw new RuntimeException(e);}}}

此时统一 的connection就可以对事务进行操作

public void transfer(int sourceid,int targetid,double money) throws SQLException{Connection conn = null;try{conn = JdbcUtils.getConnection();conn.setAutoCommit(false);AccountDao dao = new AccountDao(conn);Account a = dao.find(sourceid);Account b = dao.find(targetid);a.setMoney(a.getMoney()-money);b.setMoney(b.getMoney()+money);dao.update(a);  //sqldao.update(b);  //sqlconn.commit();}catch (Exception e) {conn.rollback();conn.commit();}finally{conn.close();}}




构造方法中添加了connection 使用dao 的时候就可以避免connection 的不统一 但是这样子并不是很好就要使用了ThreadLocale

以前没有spring的最佳事务处理方法就是使用ThreadLocaled绑定数据 翻译局部线程 其实是它是一个容器存数据 当数据链接就和当前对象绑定了 无论去到哪儿都是同一个对象了
让线程绑定链接 所有操作都是一个链接上conntion
容器内部维护的是一个map集合 就是ThreadLocale(map,关键字就是当前线程的id  当threadLocale get的时候根据id得到就是绑定的链接

重新修改jdbcutils

public class JdbcUtils{private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();private static ComboPooledDataSource ds;static{ds = new ComboPooledDataSource();}public static DataSource getDataSource(){return ds;}public static Connection getConnection() throws Exception{Connection conn = tl.get();return conn;}// 开始事物public static void startTransaction(){try{Connection conn = tl.get();if (conn == null){conn = ds.getConnection();tl.set(conn);}conn.setAutoCommit(false);} catch (Exception e){throw new RuntimeException(e);}}// 提交事物public static void commitTransaction(){try{Connection conn = tl.get();if (conn != null){conn.commit();}} catch (Exception e){throw new RuntimeException(e);}}public static void rollbackTransaction(){try{Connection conn = tl.get();if (conn != null){conn.rollback();conn.commit();}} catch (Exception e){throw new RuntimeException(e);}}public static void closeConenction(){try{Connection conn = tl.get();if (conn != null){try{conn.close();} finally{tl.remove();// 切忌这句话不能少}}} catch (Exception e){throw new RuntimeException(e);}}}

dao就可以这样子的去实现

public Account find(int id){try{QueryRunner qr=new QueryRunner();String sql="select * from account where id=?";return (Account)qr.query(JdbcUtils.getConnection(), sql,id, new BeanHandler(Account.class));} catch (Exception e){throw new RuntimeException(e);}}public void update(Account a){try{QueryRunner qr=new QueryRunner();String sql="update account set money=? where id=?";Object []params={a.getMoney(),a.getId()};qr.update(JdbcUtils.getConnection(),sql,params);} catch (Exception e){throw new RuntimeException(e);}

ThreadLocale的service这样子实现

//TreadLocalpublic void transfer(int sourceid,int targetid,double money) throws SQLException{AccountDao dao = new AccountDao();try{JdbcUtils.startTransaction();  //向当前线程上绑定一个开启事务的链接Account a = dao.find(sourceid);Account b = dao.find(targetid);a.setMoney(a.getMoney()-money);b.setMoney(b.getMoney()+money);dao.update(a);  //sqldao.update(b);  //sqlJdbcUtils.commitTransaction();}catch (Exception e) {JdbcUtils.rollbackTransaction();throw new RuntimeException(e);}finally{JdbcUtils.closeConnection();}}

注意事项:使用ThreadLcale线程范围内共享数据 线程内数据共享完后调关闭方法移除绑定 因为作为全局的使用 不移除 这个容器慢慢增加就出现内存溢出

另外就是事物边界提前了 当servlet调用service的时候完成后有请求转发到其他jsp页面上了而jsp页面又通过servlet调用了其他的service 这个时候就是事物边界提前了  从图中只能保证2个dao实现事务处理不能保证4个dao实现事务处理

就要用filter 维护里全局的ThreadLocale 当发现请求来了 ThreadLocale 绑定链接
filter做事务管理

 

 




0
原创粉丝点击