抛弃框架,如何实现分层架构下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;

}


StudentDao:

/**
 * 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;
}


定义完这两个Dao接口以后,应该是在对应的业务层(ClazzeService)中的删除班级的方法里调用这两个方法的。这样就把它们放在同一个事务中了。在调用前,我们还得做点事,弄个数据库连接工厂类(ConnectionFactory)和事务管理器类(TransactionManager)。

ConnectionFactory:

/**
 * 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());
	}
}

TransactionManager:

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


好了,可以开始进行我们的业务了(ClazzeService)。

ClazzeService:

/**
 * 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();
		}
	}
}

上面我们定义的两个Dao,我们没写它们的实现类呢,为了更明白,还是把他们(ClazzeDaoImpl,StudentDaoImpl)贴出来吧。

ClazzeDaoImpl:

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


StudentDaoImpl:

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


最后我们写个测试类(ClazzeServiceTest)进行代码的测试。

ClazzeServiceTest:

/**
 * 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);

	}

}

项目的整体结构如下图:





到此,我相信大家应该都能够弄明白我的意思了,如果还不大明白,也没关系,需要源代码的把邮箱留下…………………………………………………………







  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java高知社区

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值