現在很多項目,特別是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);}}
項目的整體結構如:
到此,我相信大家應該都能夠弄明白我的意思了,如果還不大明白,也沒關係,需要原始碼的把郵箱留下…………………………………………………………