现在很多项目,特别是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:        [email protected]
 * Site:         http://hi.csdn.net/tjcyjd
 */
package com.tjcyjd.dao;/**
 * 班级接口
 * 
 * @author yjd
 */
public interface ClazzeDao {
/** 根据id删除对应的班级 */
public void deleteClazzeByClazzeId(int clazzeId) throws DaoException;}
StudentDao:/**
 * FileName:     StudentDao.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        [email protected]
 * Site:         http://hi.csdn.net/tjcyjd
 */
package com.tjcyjd.dao;/**
 * 学生接口
 * 
 * @author yjd
 */
public interface StudentDao {
/** 根据班级id删除该班级下的所有学生 */
public void deleteStudentByClazzeId(int clazzeId) throws DaoException;
}定义完这两个Dao接口以后,应该是在对应的业务层(ClazzeService)中的删除班级的方法里调用这两个方法的。这样就把它们放在同一个事务中了。在调用前,我们还得做点事,弄个数据库连接工厂类(ConnectionFactory)和事务管理器类(TransactionManager)。
ConnectionFactory:/**
 * FileName:     ConnectionFactory.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        [email protected]
 * 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());
}
}TransactionManager:/**
 * FileName:     TransactionManager.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        [email protected]
 * 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);
}
}
}
}好了,可以开始进行我们的业务了(ClazzeService)。
ClazzeService:/**
 * FileName:     ClazzeService.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        [email protected]
 * 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();
}
}
}上面我们定义的两个Dao,我们没写它们的实现类呢,为了更明白,还是把他们(ClazzeDaoImpl,StudentDaoImpl)贴出来吧。
ClazzeDaoImpl:/**
 * FileName:     ClazzeDaoImpl.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        [email protected]
 * 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);
}
}
}StudentDaoImpl:/**
 * FileName:     StudentDaoImpl.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        [email protected]
 * 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);
}
}
}最后我们写个测试类(ClazzeServiceTest)进行代码的测试。
ClazzeServiceTest:/**
 * FileName:     ClazzeServiceTest.java
 * CreationTime: 2011-8-14
 * Author:       yjd
 * EMail:        [email protected]
 * 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); }}项目的整体结构如下图:到此,我相信大家应该都能够弄明白我的意思了,如果还不大明白,也没关系,需要源代码的把邮箱留下…………………………………………………………

解决方案 »

  1.   

    好吧,帮你顶一下。跟我那个差不多不过我那个,在逻辑上支持多层事务。另外我的事务控制是基于Annotation 
      

  2.   

    j2ee 中有个称为事务上下文的模式就是用来处理这个的,其根本是使用 ThreadLocal 和动态代理实现的。
      

  3.   

    参考下面这些帖子 49 楼中有详尽的回复:http://topic.csdn.net/u/20110329/17/afc0657a-0635-4702-b64c-a23521cf028a.html
      

  4.   


    有个问题我想请教下 LZ的代码中用到了ThreadLocal 还用到了c3p0 这样的话不会出现什么问题么 比如说ThreadLocal通过c3p0获得的Connection不能保证只是当前线程在使用 再或者c3p0对连接进行定时回收、更新的时候,会导致ThreadLocal的连接失效 这样没问题么?
      

  5.   

    http://www.iteye.com/topic/103804
      

  6.   

    我是初学的,现在正在做一个小的项目,可以把源码发给我吗?[email protected]
      

  7.   

    [email protected] 来一份
      

  8.   

    不错哦,提个建议:日志和异常最好能够打出去,比如Log4j
      

  9.   

    没觉得这个比框架简单,..............
    我不用框架,我用纯jdbc,jdbc本身是可以支持事务的,自己写到数据库操作类里,搞定好了,.....
      

  10.   

    顶   来一份 [email protected]  谢谢 
      

  11.   

    [email protected] 来一份 3Q
      

  12.   

     能把DaoFactory 类贴下吗?
    谢谢!
      

  13.   


    /**
     * FileName:     DaoFactory.java
     * CreationTime: 2011-8-13
     * Author:       yjd
     * EMail:        [email protected]
     * Site:         http://hi.csdn.net/tjcyjd
     */
    package com.tjcyjd.dao;import java.io.IOException;
    import java.util.Properties;/**
     * Dao工厂类
     * 
     * @author yjd
     */
    public class DaoFactory {
    private static Properties prop = new Properties(); static {
    try {
    prop.load(Thread.currentThread().getContextClassLoader()
    .getResourceAsStream("dao.properties"));
    } catch (IOException e) {
    e.printStackTrace();
    }
    } /**
     * 获取实例
     * 
     * @param <T>
     * @param daoName
     * @param interfaceType
     * @return
     */
    public static <T> T getInstance(String daoName, Class<T> interfaceType) {
    T obj = null; try {
    obj = interfaceType.cast(Class.forName(prop.getProperty(daoName))
    .newInstance());
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } return obj;
    }
    }
    贴出来了