自己使用cglib/jdk动态代理实现@Transaction注解(事务管理+事务传播行为)

1,456 阅读14分钟

前言

为什么重复造轮子?

  这个轮子是我去年就造了的,当时在上个东家做的是金融相关的,重视数据的一致性。我踩过一个坑(ノへ ̄、),导致我特别想知道这个spring 事务管理怎么做的。

原因一

  有一次。博主我负责的一个产品其中一个业务,主要用户使用道具,银行将资金从平台转到用户账上,银行处理成功后,发送MQ消息给我们平台;博主妹子我,写了一个幂等接口,也加了@Transaction(propagation=REQUIRED),排他锁也用了。嗯,正常来说就可以了。
  然鹅,我们过于信任RocketMQ了,没错就是阿里爸爸的那个,它消息堆积了,很短的时间内消费两次。我。。。(╯‵□′)╯︵┻━┻ ,导致给用户加了两次钱!!!
  我沉着冷静。经过一波分析,排他锁用了,也做了幂等状态判断,按理来说应该只有一条资金流水才对,所以只有一个可能,有两条线程并发了,由于事务的传播行为是REQUIRED导致它们处在同一事务里,所以不互斥都拿到了锁。于是自己立马实验一波,...( _ _)ノ|我猜想是对的,由此说明MQ重复消费了。然后立马向我主管Tony反映了,一看还真是消息堆积了好多。。。。 后面有权限的同事修复了下MQ,而我立马把注解改成@Transaction(propagation=REQUIRES_NEW),good! 嗯,其实redis的分布式锁也可以。这是原因之一。

原因二

  还是发生在上个东家的时候,技术经理喜欢分享一些技术知识。辣一次,我清楚的记得他讲了,spring是将两条线程的connection里的变量进行了复制,这样就使得两条线程的事务处在同一个connection里。听完这个,我立马就有想法要造一遍轮子。
  在此之前,多线程是比较弱的一项。我造完这个轮子,我觉得我对于多线程的理解升华了。o(*  ̄▽ ̄ * )o

轮子效果

  实现了添加@Transaction自动开启、提交、回滚,和REQUIRED、REQUIRES_NEW两种传播行为。就是抛出的异常处理信息还没控制。

知识点

AOP

  面向切面编程,和面向对象不一样,面向对象是从类出发;而面向切面是从对象的方法出发,将对象方法看作类的组件,aop关注的就是这些组件,可以类比理解Vue的设计,将页面拆分成多个组件的思想。我对它还有个更直白的理解:aop编程就是在目标方法执行前后做某些事,比如在执行前开启事务,在执行后提交或回滚事务。

  • 切面(Aspect): 切入目标业务方法前后的独立完整模块
  • 连接点(Join point):具体切入的位置,即符合匹配条件的目标业务方法
  • 切入点(Point Cut):切入点,定义匹配条件。
  • 通知(Advice):切面的具体实现方法。
    • 前置通知(Before):在目标业务方法执行前执行。
    • 环绕通知(Around):在目标业务方法执行前后都会执行。
    • 后置通知(AfterReturning):在目标业务方法正常执行后并且没有抛出异常执行。
    • 异常通知(AfterThrowing):在目标业务方法执行抛出异常时执行。
    • 最终通知(After):在目标业务方法执行后,不论是否正常执行完,最终通知都会执行。

通知执行顺序
Around->Before->Around->After->AfterReturning

事务

  事务指一组sql或一条sql组合而成。
  比如,A向B转账这个业务必须处在一个事务中:
  1.查询A账户余额;
  2.修改减少A账户余额;
  3.修改添加B账号余额;
  这几条sql处在同一个事务,要么一起成功,要么一起失败。
  处于一个事务的前提是同一个数据库连接,jdbc调用数据库的连接是Connection接口,所以如果是同一个Connection就可以使得数据操作在同一事务。

动态代理

  spring中有两种动态代理实现技术,JDK动态代理和Cglib动态代理。JDK动态代理基于接口,Cglib动态代理基于继承,都是在使用反射,在程序运行时执行。我两种都去试用了,但最后用到事务管理的是cglib。原因嘛,就是我实现完事务管理就很累了,不想再写第二遍

spring的事务传播行为

事务传播行为类型 含义
PROPAGATION_REQUIRED 如果线程调用的方法时没有正在运行的事务,就开启一个新事务;如果有,则加入到这个事务中
PROPAGATION_SUPPORTS 支持当前事务,如果当前方法没有处于事务中,就以非事务的方式运行
PROPAGATION_MANDATORY 使用当前事务,如果当前方法没有事务,则抛出异常
PROPAGATION_REQUIRES_NEW 每一条线程调用,都会新建一个事务
PROPAGATION_NOT_SUPPORTED 以非事务的方式执行,如果当前有事务,先挂起当前事务
PROPAGATION_NEVER 以非事务的方式运行,如果存在事务,抛出异常
PROPAGATION_NESTED 如果当前存在事务,则当前线程调起的新事务成为当前存在事务的嵌套子事务;如果不存在,新建一个事务

代码实现

目标

1.使用动态代理实现aop。
2.自定义一个注解@Transaction,并且有字段表示事务传播行为字段。
3.被@Transaction标记的方法自动开启事务。
4.抛出异常后,回滚事务。
5.暂时只实现REQUIRED和REQUIRES_NEW两种传播行为。
6.如果是REQUIRED,如果当前有事务,在事务结束前,后来的线程处于同一事务。
7.如果是REQUIRES_NEW,每个线程一个单独的事务。

使用到的jar包

com.springsource.net.sf.cglib-2.2.0.jar
mysql-connector-java.jar

目录结构截图

事务注解类

@Transaction.java

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

@Documented
@Retention(RetentionPolicy.RUNTIME)//注解在运行时起作用
@Target(ElementType.METHOD)//只能标记在方法上
public @interface Transcation {
	/**0=>REQUIRED 1=>REQUIRES_NEW*/
	int way() default 0;
}

增强对象类

import java.lang.reflect.Method;
import java.sql.Connection;

import util.ConnectionUtil;

public class TranscationManager {
	
	public void startTransaction(Method method){
		try {
			System.out.println("["+Thread.currentThread().getName()+"]["+method.getName()+"] 开启事务");
			ConnectionUtil.startTranscation(method);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void commit(Method method){
		try {
			System.out.println("["+Thread.currentThread().getName()+"]["+method.getName()+"] 提交事务");
			ConnectionUtil.commit(method);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void rollback(Method method){
		try {
			System.out.println("["+Thread.currentThread().getName()+"]["+method.getName()+"] 回滚事务");
			ConnectionUtil.rollback(method);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

使用Cglib/jdk生成动态代理对象

  • cglib动态代理需要的类
    • 实现MethodInterceptor方法拦截器接口
    • 增强对象TransactionManage

TranscationInterecptor.java

cglib中的方法拦截器

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import annoation.Transcation;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import transaction.TranscationManager;
import util.ConnectionUtil;
/**
 * cglib动态代理方法拦截器
 * @author Administrator
 *
 */
public class TranscationInterecptor implements MethodInterceptor{
    //增强对象(事务管理器)
	private TranscationManager transcationManager;
	public TranscationInterecptor(TranscationManager transcationManager) {
		this.transcationManager=transcationManager;
	}
	/**
	 * proxy:生成动态代理对象
	 * method:被拦截的方法
	 * args:方法参数
	 * MethodProxy:生成的代理方法对象
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
	    //获取方法上的@Transaction注解
		Transcation annotation = method.getAnnotation(Transcation.class);
		if(annotation==null){
			System.out.println("不需要开启事务");
			return methodProxy.invokeSuper(proxy, args);
		}
		//开启事务
		transcationManager.startTransaction(method);
		try {
		    //开启事务后,调用父类(目标对象)中指定方法
			Object object = methodProxy.invokeSuper(proxy, args);
			//提交事务
			transcationManager.commit(method);
			//返回父类(目标对象)返回对象
			return object;
		} catch (Exception e) {
		    //抛出异常,回滚事务
			transcationManager.rollback(method);
			e.printStackTrace();
		}
		
		return null;
	}

}

  • jdk生成动态代理需要
    • InvocationHandler增强处理器接口的实现
    • 目标对象
    • 增强对象TransactionManager

TransactionHandler.java 增强处理器

import java.lang.reflect.Method;

import annoation.Transcation;
import net.sf.cglib.proxy.InvocationHandler;
import transaction.TranscationManager;
/**
* 增强处理器
*/
public class TransactionHandler implements InvocationHandler{
    //目标对象
	private Object traget;
	//增强对象
	private TranscationManager transcationManager;
	public TransactionHandler(Object traget,TranscationManager transcationManager){
		this.traget=traget;
		this.transcationManager=transcationManager;
	}
	/**
	 * proxy:代理对象
	 * method:需要调用的方法
	 * args:参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Transcation annotation = method.getAnnotation(Transcation.class);
		if(annotation==null){
			System.out.println("不需要开启事务");
			return method.invoke(traget, args);
		}
		//开启事务
		transcationManager.startTransaction(method);
		try {
		    //调用目标对象的方法
			Object object = method.invoke(traget, args);
			//提交事务
			transcationManager.commit(method);
			return object;
		} catch (Exception e) {
		    //抛出异常回滚事务
			transcationManager.rollback(method);
			e.printStackTrace();
		}
		
		return null;
	}

}

ProxyHandler.java 使用cglib/jdk生成代理类对象

import cglib.TranscationInterecptor;
import jdk.TransactionHandler;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Proxy;
import transaction.TranscationManager;
/**
 * 生成cglib代理类
 * @author Administrator
 *
 */
public class ProxyHandler {
    /** 增强对象(事务管理器) **/
	private TranscationManager transcationManager=new TranscationManager();
	/** 方法拦截器 **/
	private TranscationInterecptor interecptor=new TranscationInterecptor(transcationManager);
	/**
	* 获取cglib生成的动态代理对象
	*/
	public Object getCglibProxy(String className) throws ClassNotFoundException{
		//获取class对象
		Class<?> cla = Class.forName(className);
		Enhancer enhancer=new Enhancer();
		//设置父类类型,即关注目标方法所属类的类型
		enhancer.setSuperclass(cla);
		//设置类加载器为目标类的类加载器
		enhancer.setClassLoader(cla.getClassLoader());
		//设置子类的回调对象
		enhancer.setCallback(interecptor);
		//生产代理对象,并利用类加载器加载
		return enhancer.create();
	}
	/**
	* 获取cglib生成的动态代理对象,重载方法
	*/
	public Object getCglibProxy(Class cla) throws ClassNotFoundException{
		//获取class对象
		Enhancer enhancer=new Enhancer();
		//设置父类类型,即关注目标方法所属类的类型
		enhancer.setSuperclass(cla);
		//设置类加载器为目标类的类加载器
		enhancer.setClassLoader(cla.getClassLoader());
		//设置子类的回调对象
		enhancer.setCallback(interecptor);
		//生产代理对象,并利用类加载器加载
		return enhancer.create();
	}
	/**
	* 获取Jdk生产的动态代理对象
	*/
	public Object getJdkProxy(Class cla) throws Exception{
	    //初始化事务增强处理器
		TransactionHandler transactionHandler=new TransactionHandler(cla.newInstance(), transcationManager);
		//获取动态代理对象实例
		Object object = Proxy.newProxyInstance(cla.getClassLoader(), cla.getInterfaces(), transactionHandler);
		return object;
	}
	
	
}

工具类

PropertiesUtil.java

读取工程目录下的properties文件

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.util.Properties;

public class PropertiesUtil {
    //Properties键值对集合
	private static Properties properties;
	static{
		try {
		    //获取当前classPath路径
			String url = PropertiesUtil.class.getResource("/").toString().replace("file:/", "");
			File file=new File(url);
			//获取工程下所有*.properties文件
			File[] files = file.listFiles(new FilenameFilter(){

				@Override
				public boolean accept(File dir, String name) {
					if(name.endsWith(".properties")){
						return true;
					}
					return false;
				}
				
			});
			properties=new Properties();
			for(File f:files){
				Properties p=new Properties();
				FileInputStream in=new FileInputStream(f);
				p.load(in);
				properties.putAll(p);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	* 根据key获取配置的value
	*/
	public String getValue(String key,String defaultValue){
		String value = properties.getProperty(key, defaultValue);
		return value;
	}
}

BeanUtil.java

拷贝对象字段值到另一个对象的工具类,REQUIRED类型的传播行为需要用到,Connection之间的拷贝。

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class BeanUtil {
	public static void copyProperties(Object srouce,Object traget) throws Exception{
	    //获取class对象
		Class<? extends Object> sCla = srouce.getClass();
		Class<? extends Object> tCla = traget.getClass();
		//获取所有的字段属性
		Field[] fields = sCla.getDeclaredFields();
		for(Field f:fields){
		    //final,static修饰的字段不拷贝
			if(Modifier.isFinal(f.getModifiers())&&Modifier.isStatic(f.getModifiers())){
				continue;
			}
			//源对象class的字段授权
			f.setAccessible(true);
			//获取目标对象class的同名字段
			Field field = tCla.getDeclaredField(f.getName());
			//授权
			field.setAccessible(true);
			//给目标对象字段赋值
			field.set(traget, f.get(srouce));
		}
	}
}

ConnectionUtil.java 核心

实现事务管理,事务传播行为,jdbc操作数据的核心 ConnectionUtil做的事有:

  • 获取jdbc的Connection对象
    1. 从一条线程的维度上看,单个线程也有可能调起多个事务,也有传播行为,比如线程1执行方法A,方法A调用方法B,两个方法都有@Transaction注解
  • 开启事务
    1. 传播行为是REQUIRED
      • 需要维护一个已经开启connection集合,connection对象地址不同,但是指向同一个数据库连接。
    2. 传播行为是REQUIRES_NEW
      • 只维护一个给方法调用栈开启的connection就好
  • 提交回滚事务
    1. 如果传播行为是REQUIRED。
      • 多个connection指向的是同一个数据库连接,最后执行完的那个connection才是真正提交事务的。对于回滚,是哪条Connection所处的地方抛出异常,就是哪个回滚事务,由于指向的是同一个数据库连接,所以其他Connection指向的数据库连接也是一起回滚了的。
    2. 如果传播行为是REQUIRES_NEW
      • 多个connection互不关联,只有异常使它们产生关联
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;

import annoation.Transcation;
/**
 * @author Administrator
 *
 */
public class ConnectionUtil {
	/**
	 * 单条线程方法调用链开启的Connection集合,单条线程维护一个开启Connection的栈,先开启的,最后关闭
	 * methodA 调用 methodB ,methodB调用methodC,从而形成ConnectionA,ConnectionB,ConnectionC
	 * */
	private static ThreadLocal<Stack<Connection>> threadLocal=new ThreadLocal<>();
	/**
	 * 多个线程调用同一个方法产生的connection队列
	 * 线程1执行方法A,产生connection1;线程2执行方法A,产生connection2
	 * 先开启的先执行完
	 * 只有当传播行为是REQUIRED才会维护该集合
	 * */
	private static Map<Method,Deque<Connection>> openConnList=new ConcurrentHashMap<>();
	
	private static PropertiesUtil propertiesUtil=new PropertiesUtil();
	
	private static String url=propertiesUtil.getValue("url", null);
	private static String driver=propertiesUtil.getValue("driver", null);
	private static String username=propertiesUtil.getValue("username", null);
	private static String password=propertiesUtil.getValue("password", null);
	
	static{
		try {
			Class.forName(driver);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 重构成原型模式-深拷贝/浅拷贝 ?
	 * 对于多条线程在同一个事务的传播行为
	 * 某条线程回滚,其他线程也要回滚,从这个方向看,多条线程操作的是同一个对象--浅拷贝
	 * 对于多线程不在同一个事务
	 * 线程之间事务状态互不影响,操作的不是同一对象,可能同一时刻各自事务的状态也不同--开辟新对象新空间,不能用原型模式
	 * 没法实现原型模式,由于connection是mysql定义,无法使用多态
	 * @param method
	 * @return
	 * @throws Exception
	 */
	public static Connection getConnection(Method method) throws Exception{
		if(method==null){
			return threadLocal.get().peek();
		}
		//获取注解
		Transcation transcation = method.getAnnotation(Transcation.class);
	
		if(threadLocal.get()==null){
			Stack<Connection> stack=new Stack<>();
			threadLocal.set(stack);
		}
		//当前线程没有已开启的connection
		if(threadLocal.get().isEmpty()){
			Connection connection=DriverManager.getConnection(url, username, password);
			threadLocal.get().push(connection);
		}
		//单条线程执行的方法再调用别的方法开启的Connection
		else if(transcation.way()==0&&!threadLocal.get().isEmpty()){//被调用的方法且传播行为是0,相当于spring的REQUIRED
		   //同一条线程内就不需要拷贝Connection了
			Connection peek = threadLocal.get().peek();
			threadLocal.get().push(peek);
		}else if(transcation.way()==1&&!threadLocal.get().isEmpty()){//被调用的方法且传播行为是1,相当于spring的REQUIRES_NEW
			Connection connection=DriverManager.getConnection(url, username, password);
			threadLocal.get().push(connection);
		}
		//返回栈顶元素,即最里面Connection
		return threadLocal.get().peek();
	}
	public  static void startTranscation(Method method) throws Exception{
		Transcation transcation = method.getAnnotation(Transcation.class);
		Connection connection=null;
		if(transcation.way()==0){
			//由于openConnList是静态变量需要同步锁,ConnectionUtil.class作为锁颗粒度太大,应该用openConnList+双重检查机制
			synchronized (ConnectionUtil.class) {
				//该方法connection队列为空
				if(openConnList.get(method)==null){
					Deque<Connection> value=new LinkedList<>();
					openConnList.put(method, value);
				}
				//获取队首元素
				connection = openConnList.get(method).peek();
				System.out.println("["+Thread.currentThread().getName()+"] openConnFirst>>"+connection);
				//新开一个connection
				Connection connection2 = getConnection(method);
				//当前没有其它线程在method方法上开启过事务
				if(connection==null){
					connection=connection2;
				}else{//有线程开启过事务,将已开启的connection的值赋给新开的connection2(对于多线程)
					//由于是多个线程同处一个事务,需要拷贝
					BeanUtil.copyProperties(connection, connection2);
					Connection pop = threadLocal.get().pop();
					pop=connection2;
					threadLocal.get().push(pop);
				}
				//入队
				openConnList.get(method).offer(connection2);
			}
		}else{
			connection = getConnection(method);
		}
		System.out.println("["+Thread.currentThread().getName()+"] start connection >>"+connection);
		
		connection.setAutoCommit(false);
	}
	public static void commit(Method method) throws Exception{
		if(threadLocal.get()==null){
			return ;
		}
		//栈顶元素,获取当前线程最后开启的connection
		Connection connection = threadLocal.get().peek();
		Transcation transcation = method.getAnnotation(Transcation.class);
		if(transcation.way()==0){
			synchronized (ConnectionUtil.class) {
				//多线程情况下,method上最后一条线程执行完才会真正提交事务
				if(openConnList.get(method).size()==1){
					if(!connection.isClosed()){
						System.out.println("["+Thread.currentThread().getName()+"] method>>"+method.getName()+" commit>>");
						if(threadLocal.get().size()==1){//对于单线程本身,最先开启connection的方法执行完才是真正提交事务(即栈底元素)
						    if(!connection.isClosed()){
						        connection.commit();
							    connection.close();   
						    }
							threadLocal.set(null);
						}else{//单线程中,不是最先开启connection(非栈底元素)
							threadLocal.get().pop();
						}
					}
				}
				//多个线程的connection出队
				if(!openConnList.get(method).isEmpty()){
					openConnList.get(method).pop();
				}
			}
		}else{
		    if(!connection.isClosed()){
		        connection.commit();
			    connection.close();
		    }
			
			if(threadLocal.get().size()>1){//非最先开启的connection
				threadLocal.get().pop();//单个线程中connection出栈
			}else{
				threadLocal.set(null);
			}
		}
	}
	public static void rollback(Method method) throws Exception{
		if(threadLocal.get()==null){
			return ;
		}
		//取出栈顶元素
		Connection connection = threadLocal.get().pop();
		Transcation transcation = method.getAnnotation(Transcation.class);
		if(transcation.way()==0){
			synchronized (ConnectionUtil.class) {
				if(!openConnList.get(method).isEmpty()){
					openConnList.get(method).pop();
				}
				if(connection!=null&&!connection.isClosed()){
					System.out.println("["+Thread.currentThread().getName()+"] rollback>>");
					connection.rollback();
					connection.close();
				}
			}
		}else{
			if(connection!=null&&!connection.isClosed()){
				connection.rollback();
				connection.close();
			}
		}
		if(threadLocal.get().isEmpty()){
			threadLocal.set(null);
		}
//		threadLocal.set(null);
	}
	public static void close(ResultSet rs,PreparedStatement ps,Connection conn){
		try {
			if(rs!=null){
				rs.close();
			}
			if(ps!=null){
				ps.close();
			}
			if(conn!=null){
				if(conn.getAutoCommit()){
					conn.close();
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

业务相关

User.java

public class User {
	private Integer id;
	private String userName;
	private Integer age;
	public User(){}
	public User(String UserName,Integer age){
		this.userName=UserName;
		this.age=age;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", userName=" + userName + ", age=" + age + "]";
	}
	
}

UserService.java

import pojo.User;

public interface UserService {
	User test(Integer id,User record,Integer mark) throws Exception;
	void insert() throws Exception;
}

UserServiceImpl.java

import annoation.Transcation;
import dao.UserDAO;
import pojo.User;
import proxy.ProxyHandler;

public class UserServiceImpl implements UserService{
	private UserDAO userDAO=new UserDAO();
	ProxyHandler handler=new ProxyHandler();
	/**
	 * REQUIRES_NEW
	 * @param id
	 * @param record
	 * @return
	 * @throws Exception
	 */
	@Transcation(way=0)
	public User test(Integer id,User record,Integer mark) throws Exception {
		System.out.println("sql执行中");
		User user = userDAO.selectById(id);

		userDAO.insert(new User(record.getUserName()+"_method-test",25));

		System.out.println("["+Thread.currentThread().getName()+"] user>>"+user);

		UserServiceImpl userService = (UserServiceImpl) handler.getCglibProxy(UserServiceImpl.class);
		userService.insert(record,mark);

		System.out.println("["+Thread.currentThread().getName()+"] 插入数据主键>>"+user.getId());
		return user;
	}
	/**
	 * REQUIRED
	 * @param user
	 * @param mark
	 * @throws Exception
	 */
	@Transcation(way=0)
	public void insert(User user,Integer mark) throws Exception{
		String userName = user.getUserName();
		user.setUserName(userName+"_method-insert");
		userDAO.insert(user);
		if(mark==1){
			throw new Exception("线程1抛异常");
		}
	}
	@Override
	@Transcation
	public void insert() throws Exception {
		userDAO.insert(new User("ttttt",22));
		int s=1/0;
	}
	
}

UserDAO.java

import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import pojo.User;
import util.ConnectionUtil;

public class UserDAO {
	public User selectById(Integer id) throws Exception{
		Connection connection = ConnectionUtil.getConnection(null);
		
		PreparedStatement prepareStatement = connection.prepareStatement("select * from user where id=? for update");
		prepareStatement.setInt(1, id);
		ResultSet resultSet = prepareStatement.executeQuery();
		User user=new User();
		if(resultSet.next()){
			user.setId(resultSet.getInt("id"));
			user.setUserName(resultSet.getString("user_name"));
			user.setAge(resultSet.getInt("age"));
		}
		/*prepareStatement = connection.prepareStatement("insert into user (user_name,age) values(?,?)");
		prepareStatement.setString(1,new String((Thread.currentThread().getName()+" selectById").getBytes(),"utf-8"));
		prepareStatement.setInt(2, 22);
		int row = prepareStatement.executeUpdate();*/

		ConnectionUtil.close(resultSet, prepareStatement, connection);
		return user;
	}
	public User insert(User user) throws Exception{
		Connection connection = ConnectionUtil.getConnection(null);
		PreparedStatement prepareStatement = connection.prepareStatement("insert into user (user_name,age) values(?,?)");
		prepareStatement.setString(1,new String((user.getUserName()+" insert").getBytes(),"utf-8"));
		prepareStatement.setInt(2, user.getAge());
		int row = prepareStatement.executeUpdate();
		ResultSet generatedKeys = prepareStatement.getGeneratedKeys();
		if(generatedKeys.next()){
			user.setId(generatedKeys.getInt(1));
		}
		return user;
	}
}

client.java 测试

import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import pojo.User;
import util.ConnectionUtil;

public class UserDAO {
	public User selectById(Integer id) throws Exception{
		Connection connection = ConnectionUtil.getConnection(null);
		
		PreparedStatement prepareStatement = connection.prepareStatement("select * from user where id=? for update");
		prepareStatement.setInt(1, id);
		ResultSet resultSet = prepareStatement.executeQuery();
		User user=new User();
		if(resultSet.next()){
			user.setId(resultSet.getInt("id"));
			user.setUserName(resultSet.getString("user_name"));
			user.setAge(resultSet.getInt("age"));
		}
		/*prepareStatement = connection.prepareStatement("insert into user (user_name,age) values(?,?)");
		prepareStatement.setString(1,new String((Thread.currentThread().getName()+" selectById").getBytes(),"utf-8"));
		prepareStatement.setInt(2, 22);
		int row = prepareStatement.executeUpdate();*/

		ConnectionUtil.close(resultSet, prepareStatement, connection);
		return user;
	}
	public User insert(User user) throws Exception{
		Connection connection = ConnectionUtil.getConnection(null);
		PreparedStatement prepareStatement = connection.prepareStatement("insert into user (user_name,age) values(?,?)");
		prepareStatement.setString(1,new String((user.getUserName()+" insert").getBytes(),"utf-8"));
		prepareStatement.setInt(2, user.getAge());
		int row = prepareStatement.executeUpdate();
		ResultSet generatedKeys = prepareStatement.getGeneratedKeys();
		if(generatedKeys.next()){
			user.setId(generatedKeys.getInt(1));
		}
		return user;
	}
}