抛弃框架,如何实现分层架构下JDBC事务的控制
现在很多项目,特别是web项目,为了提高开发效率,基本上都用上了框架,struts1,struts2.,spring,hibernate,springmvc,ibatise等等,在事务处理方面,spring的尤其突出,它对事务做了很好的封装,通过AOP的配置,可以灵活的配置在任何一层。但是很多时候,基于需求和应用方面考虑,直接使用JDBC进行事务控制还是很有必要的。
事务应该是以业务逻辑为基础的;一个完整的业务应该对应业务层里的一个方法,而不应该是多个方法;如果业务执行过程出现异常,则整个事务应该回滚;所以,应该事务控放在业务层里;然而持久层的设计应该遵循一个很重要的原则:持久层应该保证操作的原子性,就是说持久层里的每个方法都应该是不可以分割的,也就是说数据库的连接自始至终都应该是同一个连接,而不是执行完某个Dao操作完毕并且数据库连接关闭后又重新打开一个新的数据库连接执行另一个Dao操作!
上面的说法可能有点抽象,举个简单的例子来说:针对班级(clazze)和学生(student)的操作,要想删除某个班级,就需要先把该班级下的所有学生删除,再删除班级,这两个操作是应该放在同一个事务里的,要么同时删除学生和班级成功,要么同时失败,不能学生的数据被删除没了,而班级却没被删除,这就是上面所说的----原子性。
可能上面的描述还是有点抽象,没关系,我们用代码说话。
先定义两个Dao接口,一个是班级的接口(ClazzeDao),一个是学生的接口(StudentDao),里面只提供删除的功能。
ClazzeDao:
/** * FileName: ClazzeDao.java * CreationTime: 2011-8-14 * Author: yjd * EMail: 908599713@qq.com * Site: http://hi.csdn.net/tjcyjd */package com.tjcyjd.dao;/** * 班级接口 * * @author yjd */public interface ClazzeDao { /** 根据id删除对应的班级 */ public void deleteClazzeByClazzeId(int clazzeId) throws DaoException;}/** * FileName: StudentDao.java * CreationTime: 2011-8-14 * Author: yjd * EMail: 908599713@qq.com * Site: http://hi.csdn.net/tjcyjd */package com.tjcyjd.dao;/** * 学生接口 * * @author yjd */public interface StudentDao { /** 根据班级id删除该班级下的所有学生 */ public void deleteStudentByClazzeId(int clazzeId) throws DaoException;}/** * FileName: ConnectionFactory.java * CreationTime: 2011-8-14 * Author: yjd * EMail: 908599713@qq.com * Site: http://hi.csdn.net/tjcyjd */package com.tjcyjd.commom;import java.io.IOException;import java.sql.Connection;import java.sql.SQLException;import java.util.Properties;import javax.sql.DataSource;import com.mchange.v2.c3p0.DataSources;/** * 数据库连接工厂 * * @author yjd */public class ConnectionFactory { private static Properties prop = new Properties(); // 数据源 private static DataSource ds = null; // 用来把数据库连接绑定到当前线程上的变量 private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); static { try { prop.load(Thread.currentThread().getContextClassLoader() .getResourceAsStream("jdbc.properties")); } catch (IOException e) { e.printStackTrace(); System.out.println("在classpath下没有找到jdbc.properties文件"); } // 这里使用的是c3p0连接 try { Class.forName("com.mysql.jdbc.Driver"); DataSource unpooled = DataSources.unpooledDataSource(prop .getProperty("url"), prop.getProperty("user"), prop .getProperty("password")); ds = DataSources.pooledDataSource(unpooled); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } /** * 获取数据库的Connection对象 * * @return */ public static synchronized Connection getConnection() { Connection conn = tl.get(); // 当前线程取出连接实例 if (null == conn) { try { conn = ds.getConnection(); // 从连接池中取出一个连接实例 tl.set(conn); // 把它绑定到当前线程上 } catch (SQLException e) { e.printStackTrace(); } } return conn; } public static synchronized TransactionManager getTranManager() { return new TransactionManager(getConnection()); }}
/** * FileName: TransactionManager.java * CreationTime: 2011-8-14 * Author: yjd * EMail: 908599713@qq.com * Site: http://hi.csdn.net/tjcyjd */package com.tjcyjd.commom;import java.sql.Connection;import java.sql.SQLException;import com.tjcyjd.dao.DaoException;/** * 事务管理器类 * * @author yjd */public class TransactionManager { private Connection conn; protected TransactionManager(Connection conn) { this.conn = conn; } /** * 开启事务 * * @throws DaoException */ public void beginTransaction() throws DaoException { try { if (null != conn && !conn.isClosed()) { conn.setAutoCommit(false); // 把事务提交方式改为手工提交 } } catch (SQLException e) { throw new DaoException("开户事务时出现异常", e); } } /** * 提交事务并关闭连接 * * @throws DaoException */ public void commitAndClose() throws DaoException { try { conn.commit(); // 提交事务 System.out.println("提交事务"); } catch (SQLException e) { throw new DaoException("开启事务时出现异常", e); } finally { close(conn); } } /** * 回滚并关闭连接 * * @throws DaoException */ public void rollbackAndClose() throws DaoException { try { conn.rollback(); System.out.println("回滚事务"); } catch (SQLException e) { throw new DaoException("回滚事务时出现异常", e); } finally { close(conn); } } /** * 关闭连接 * * @param conn * @throws DaoException */ private void close(Connection conn) throws DaoException { if (conn != null) { try { conn.close(); } catch (SQLException e) { throw new DaoException("关闭连接时出现异常", e); } } }}/** * FileName: ClazzeService.java * CreationTime: 2011-8-14 * Author: yjd * EMail: 908599713@qq.com * Site: http://hi.csdn.net/tjcyjd */package com.tjcyjd.service;import com.tjcyjd.commom.ConnectionFactory;import com.tjcyjd.commom.TransactionManager;import com.tjcyjd.dao.ClazzeDao;import com.tjcyjd.dao.DaoException;import com.tjcyjd.dao.DaoFactory;import com.tjcyjd.dao.StudentDao;/** * 班级操作业务类 * * @author yjd */public class ClazzeService { private ClazzeDao clazzeDao = DaoFactory.getInstance("clazzeDao", ClazzeDao.class); private StudentDao studentDao = DaoFactory.getInstance("studentDao", StudentDao.class); /** * 删除指定ID的班级 * * @param clazzeId */ public void deleteClazze(int clazzeId) { TransactionManager tx = ConnectionFactory.getTranManager(); try { tx.beginTransaction(); // 删除指定班级下的所有学生 studentDao.deleteStudentByClazzeId(clazzeId); // 删除指定班级 clazzeDao.deleteClazzeByClazzeId(clazzeId); // 提交事务并关闭连接 tx.commitAndClose(); } catch (DaoException e) { e.printStackTrace(); // 异常回滚 tx.rollbackAndClose(); } }}/** * FileName: ClazzeDaoImpl.java * CreationTime: 2011-8-14 * Author: yjd * EMail: 908599713@qq.com * Site: http://hi.csdn.net/tjcyjd */package com.tjcyjd.dao.impl;import java.sql.Connection;import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import com.tjcyjd.commom.ConnectionFactory;import com.tjcyjd.dao.ClazzeDao;import com.tjcyjd.dao.DaoException;/** * 班级接口实现类 * * @author yjd */public class ClazzeDaoImpl implements ClazzeDao { private QueryRunner qr = new QueryRunner(); /** 删除指定班级 */ public void deleteClazzeByClazzeId(int clazzeId) throws DaoException { Connection conn = ConnectionFactory.getConnection(); // 故意错写sql语句,多了个*。 String sql = "delete * from clazze where clazze_id=?"; try { qr.update(conn, sql, clazzeId); System.out.println("成功执行了deleteStudentByClazzeId方法,但未提交事务"); } catch (SQLException e) { throw new DaoException("删除指定ID的部门时出现异常", e); } }}
/** * FileName: StudentDaoImpl.java * CreationTime: 2011-8-14 * Author: yjd * EMail: 908599713@qq.com * Site: http://hi.csdn.net/tjcyjd */package com.tjcyjd.dao.impl;import java.sql.Connection;import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import com.tjcyjd.commom.ConnectionFactory;import com.tjcyjd.dao.DaoException;import com.tjcyjd.dao.StudentDao;/** * 学生接口实现类 * * @author yjd */public class StudentDaoImpl implements StudentDao { private QueryRunner qr = new QueryRunner(); /** 删除指定班级下的所有学生 */ public void deleteStudentByClazzeId(int clazzeId) throws DaoException { Connection conn = ConnectionFactory.getConnection(); String sql = "delete from student where clazze_id=?"; try { qr.update(conn, sql, clazzeId); System.out.println("成功执行了deleteStudentByClazzeId方法,但未提交事务"); } catch (SQLException e) { throw new DaoException("删除指定ID的部门时出现异常", e); } }}/** * FileName: ClazzeServiceTest.java * CreationTime: 2011-8-14 * Author: yjd * EMail: 908599713@qq.com * Site: http://hi.csdn.net/tjcyjd */package com.tjcyjd.service;/** * ClazzeService的测试类 * * @author yjd */public class ClazzeServiceTest { /** * 主方法 * * @param args */ public static void main(String[] args) { testDeleteDept(2); } /** * 删除指定班级 */ public static void testDeleteDept(int clazzeId) { ClazzeService sf = new ClazzeService(); sf.deleteClazze(clazzeId); }}