声明式事务的一种实现

熟悉Spring和“楼上”平台的同事都知道,在基于这两种框架进行开发时,如果某个方法需要放到事务中执行,一般只需要在该方法头上打个“Trans”标记就可以了,这种实现方式,由于其低侵入式、简单、易用等特点,颇受广大开发者的喜爱,然而其内在的实现原理是什么呢?相信很多开发者并不清楚,本文就为你揭开这层神秘的面纱,自己动手实现这种看似神奇的效果。

 

关键知识点:

1、动态代理,通过动态代理,切入 事务启动、提交、回滚处理等操作。

2、反射,通过反射,动态创建实例对象,动态执行方法、并根据注解信息执行其他一些操作。

3、线程局部变量,通过线程局部变量实现在不同方法间完成事务的传递。

示例:

1、创建一个注解Trans,该注解所标注的方法将放在事务中执行,如果方法执行出错(抛异常),则回滚事务,如果成功执行,则提交事务。

package framework.db;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Trans {

}

2、创建一个数据库连接的工具类,数据库连接统一从该工具类中取得,获取数据库连接时,首先从线程局部变量中查找,如果线程局部变量中不存在,则通过工具类新创建一个,并放入线程局部变量。

扫描二维码关注公众号,回复: 1383103 查看本文章
package framework.db;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DBUtil {

	private static String url = null;
	private static String driver = null;
	private static String username = null;
	private static String password = null;
	
	static{
		Properties p = new Properties();//加载数据源配置文件
		InputStream inputStream = null;
		try {
			inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("dataSource.properties");
			p.load(inputStream);
			url = p.getProperty("url");
			driver = p.getProperty("driver");
			username = p.getProperty("username");
			password = p.getProperty("password");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	//线程局部变量
	protected static ThreadLocal<Connection>  threadLocalCon = new ThreadLocal<Connection>();
	
	/*
	 * 获取数据库连接
	 */
	public static Connection getCon() {

		Connection con = threadLocalCon.get();
		try {
			if (con == null || con.isClosed()) {
				Class.forName(driver);
				con = DriverManager.getConnection(url, username, password);
				threadLocalCon.set(con);
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return con;
	}
	
	/*
	 * 关闭结果集 ResultSet
	 */
	public static void closeResultSet(ResultSet rs){
		if(rs != null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	/*
	 * 关闭 句柄
	 */
	public static void closeStatement(Statement st){
		if(st != null){
			try {
				st.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	/*
	 * 在事务中调用dao层方法时,会首先设置事务自动提交为false,该场景下,关闭连接由事务管理器负责。
	 * 如果dao层方法没有在事务中执行,则此时事务自动提交为true,该场景下,由本方法负责关闭连接。
	 */
	public static void closeConnectionIfAutoCommit(Connection con){
		if(con != null){
		try {
			if(con.getAutoCommit()){	
				con.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	}
	/*
	 * 依次关闭ResultSet、Statement、Connection
	 */
	public static void close(ResultSet rs,Statement st,Connection con){
		closeResultSet(rs);
		closeStatement(st);
		closeConnectionIfAutoCommit(con);
	}
}

3、创建事务管理器,事务管理器负责事务的开启、提交、回滚。

package framework.db;

import java.sql.Connection;
import java.sql.SQLException;

public class TransactionManager {

	private Connection con;
	private TransactionManager(Connection con){
		this.con = con;
	}
	
	/*
	 * 开启事务
	 */
	public void beginTransaction(){
		try {
			con.setAutoCommit(false);
		} catch (SQLException e) {
			throw new RuntimeException("开启事务失败!",e);
		}
	}
	/*
	 * 提交事务
	 */
	public void commitTransaction(){
		try {
			con.commit();
		} catch (SQLException e) {
			throw new RuntimeException("提交事务失败!",e);
		}finally{		
			closeConnection();
			DBUtil.threadLocalCon.remove();//将数据库连接从线程局部变量中卸载。
			}
	}
	/*
	 * 回滚事务
	 */
	public void rollbackTransaction(){
		try {
			con.rollback();
		} catch (SQLException e) {
			throw new RuntimeException("回滚事务失败!",e);
		}finally{
			closeConnection();
			DBUtil.threadLocalCon.remove();//将数据库连接从线程局部变量中卸载。
		}
	}
	/*
	 * 获取事务管理器
	 */
	public static TransactionManager getTransManager(){
		return new TransactionManager(DBUtil.getCon());
	}
	/*
	 * 关闭数据库连接,仅限事务管理器内部使用,故private
	 */
	private void closeConnection(){
		if(con != null){
			try {
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

4、创建方法拦截器,负责拦截指定的方法,拦截到指定方法后,通过反射取得该方法的注解信息,如果该方法被Trans注解,则将该方法置于事务中执行。

package framework.intercept;

import java.lang.reflect.Method;
import framework.db.Trans;
import framework.db.TransactionManager;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;


public class MyMethodInterceptor implements MethodInterceptor{

	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxyMethod) throws Throwable {
		
		Trans myAnnotation = method.getAnnotation(Trans.class);
		Object returnValue = null;
		TransactionManager manger = null;
		System.out.println("拦截方法:"+method);
		try {		
			//开启事务
			if(myAnnotation != null){
				manger = TransactionManager.getTransManager();
				manger.beginTransaction();
				System.out.println("开启事务"+method);
			}
			
			//执行源对象的method方法
			System.out.println("---before---"+method);
			returnValue = proxyMethod.invokeSuper(obj, args);
			System.out.println("---after---"+method);
			
			//提交事务
			if(myAnnotation != null){
				manger.commitTransaction();
				System.out.println("提交事务");
			}
		} catch (Exception e) {
			
			System.out.println("执行方法"+method+"抛异常了");
			
			//回滚事务
			if(myAnnotation != null){
				manger.rollbackTransaction();
				System.out.println("回滚事务");
			}
		}	
		return returnValue;
	}
}

5、创建对象工厂,负责生成指定类的对象或代理对象(Service层的生成代理对象,非Service层的生成普通实例对象),代理对象执行时,会首先提交给第4步创建的方法拦截器处理。

package framework.factory;

import java.util.HashMap;
import java.util.Map;

import net.sf.cglib.proxy.Enhancer;
import framework.intercept.MyMethodInterceptor;
/*
 * 实例工厂类,用于统一管理cmd、service、dao的实例。
 */
public class ObjectFactory {

	//创建一个对象池
	private static Map<String,Object> objPool = new HashMap<String, Object>();
	
	/*
	 * 根据类的包路径名称,返回该类的一个实例。
	 * 约定:业务逻辑层(Service层)的类名endsWith Service
	 * 为Service层创建代理对象,非Service层直接创建实例。
	 */
	public static Object getObject(String clazz){
		
		Object obj = null;
		if(objPool.containsKey(clazz)){//如果对象池中已存在,则直接从对象池中获取。
			obj = objPool.get(clazz);
		}else{
			try {	
				if(clazz.endsWith("Service")){
					System.out.println("创建"+clazz+"代理实例对象!");
					
					//如果对象池中不存在,则动态创建一个该类的实例,并将新创建的实例放入对象池。
					Enhancer hancer = new Enhancer();
					
					//设置代理对象的父类
					hancer.setSuperclass(Class.forName(clazz));
					
					//设置回调对象,即调用代理对象里面的方法时,实际上执行的是回调对象里的intercept方法。
					hancer.setCallback(new MyMethodInterceptor());
					
					//创建代理对象
					obj = hancer.create();
				}else{
					System.out.println("创建"+clazz+"普通实例对象!");
					obj = Class.forName(clazz).newInstance();
				}
			
				//将新创建的对象放入代理对象池。
				objPool.put(clazz, obj);
			}catch (Exception e) {
				throw new RuntimeException("创建 "+clazz+" 实例对象失败!",e);
			}
		}
		return obj;
	}
}

6、使用示例:

package folder.service;

import java.util.List;

import file.dao.FileDao;
import folder.bean.Folder;
import folder.dao.FolderDao;
import framework.db.Trans;
import framework.factory.ObjectFactory;

public class FolderService {

	//通过对象工厂拿到FolderDao和FileDao的实例对象
	private FolderDao folderDao = (FolderDao) ObjectFactory.getObject(FolderDao.class.getName());
	private FileDao fileDao = (FileDao) ObjectFactory.getObject(FileDao.class.getName());
	
	@Trans
	public void insert(Folder folder) {
		folderDao.insert(folder);
	}
	/*
	 * 根据档案袋名称进行查询
	 */
	public List<Folder> queryByFolderName(String foldName){
		return folderDao.queryByFolderName(foldName);
	}
	/*
	 * 根据档案袋Id删除档案袋信息
	 */
	@Trans
	public void deleteByFolderId(String folderId) {	
		fileDao.deleteByFolderId(folderId);
		folderDao.deleteByFolderId(folderId);
	}
}

 7、测试

import folder.bean.Folder;
import folder.service.FolderService;
import framework.factory.ObjectFactory;

public class Test {
	
	public static void main(String[] args) {

		FolderService s = (FolderService)ObjectFactory.getObject(FolderService.class.getName());	
		Folder folder = new Folder();
		folder.setFolderId("001");
		folder.setFolderCode("code001");
		folder.setFolderName("学生档案");
		folder.setFolderYear("2012");
		s.insert(folder);
	}
}

首次执行,预期结果应该是成功插入一条记录,并成功提交事务。

创建folder.service.FolderService代理实例对象!
创建folder.dao.FolderDao普通实例对象!
创建file.dao.FileDao普通实例对象!
拦截方法:public void folder.service.FolderService.insert(folder.bean.Folder)
开启事务public void folder.service.FolderService.insert(folder.bean.Folder)
---before---public void folder.service.FolderService.insert(folder.bean.Folder)
---after---public void folder.service.FolderService.insert(folder.bean.Folder)
提交事务

 再次执行,预期结果,由于数据库中已经存在该记录,再次插入会由于主键唯一性约束而插入失败,事务回滚。

创建folder.service.FolderService代理实例对象!
创建folder.dao.FolderDao普通实例对象!
创建file.dao.FileDao普通实例对象!
拦截方法:public void folder.service.FolderService.insert(folder.bean.Folder)
开启事务public void folder.service.FolderService.insert(folder.bean.Folder)
---before---public void folder.service.FolderService.insert(folder.bean.Folder)
回滚事务
Exception in thread "main" java.lang.RuntimeException: 出错了!
	at framework.intercept.MyMethodInterceptor.intercept(MyMethodInterceptor.java:50)
	at folder.service.FolderService$$EnhancerByCGLIB$$9a9d9f19.insert(<generated>)
	at Test.main(Test.java:15)
Caused by: java.lang.RuntimeException: 保存档案袋时出错!
	at folder.dao.FolderDao.insert(FolderDao.java:50)
	at folder.service.FolderService.insert(FolderService.java:19)
	at folder.service.FolderService$$EnhancerByCGLIB$$9a9d9f19.CGLIB$insert$2(<generated>)
	at folder.service.FolderService$$EnhancerByCGLIB$$9a9d9f19$$FastClassByCGLIB$$8fcee500.invoke(<generated>)
	at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
	at framework.intercept.MyMethodInterceptor.intercept(MyMethodInterceptor.java:35)
	... 2 more
Caused by: java.sql.SQLException: ORA-00001: 违反唯一约束条件 (APITEST.T_FOLDER_PK)

	at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:74)
	at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:131)
	at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:204)
	at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:455)
	at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:413)
	at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:1034)
	at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:194)
	at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:953)
	at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1222)
	at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3387)
	at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3468)
	at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1062)
	at folder.dao.FolderDao.insert(FolderDao.java:48)
	... 7 more

  

http://huangqiqing123.iteye.com/blog/1443446

猜你喜欢

转载自huangqiqing123.iteye.com/blog/1443446
今日推荐