原生的jdbc 对事务管理也是比较繁琐的, 需要手工进行提交和回滚, 还要一堆try-catch. 而熟悉spring 的同学都知道, spring采用了声明式事务方式来管理事务, 使事务管理变得很简单. Spring 事务很强大, 笔者这里仅使用jdbc 来模拟简单的几个属性.

  1. 声明式事务方案设计

  声明式事务主要依据java 动态代理实现

  通过将Connection 存放在ThreadLocal 变量中, 来解决并发问题. Spring 底层也是用的ThreadLocal.

  通过记录Connection 的创建者, 来解决事务的嵌套问题.

  自定义注解@EnableTranscation: 用于标明方法是否开启事务

  Service工厂: 用于模拟Spring容器创建Bean过程, 如果Service 中包含使用@EnableTranscation修饰的方法, 则创建Service的代理对象, 否则返回Service 实例

  自定义Dao时, 不能直接创建Connection, 需要获取当前线程中保存的Connection.

  2. 连接池管理

  笔者对数据库的连接采用c3p0 连接池.

  2.1 c3po 配置

  root

  root

  com.mysql.jdbc.Driver

  jdbc:mysql://localhost:3306/learn-jdbc?characterEncoding=UTF-8

  10

  5

  5

  50

  100

  10

  2.2 数据库连接工具类

  封装获取数据库连接和关闭数据库连接资源的方法

  public class DbConnUtil {

  // c3P0配置名

  private static final String c3p0PoolName = "myC3p0Pool";

  // 配置数据源

  private static final DataSource dataSource = new ComboPooledDataSource(c3p0PoolName);

  // 配置本地连接

  private static ThreadLocal txConnectionLocal = new ThreadLocal<>();

  /** 获取数据库连接

  * @param autoCommitTx 是否开启提供提交事务

  * @return Connection 数据库连接

  * @since 1.0

  * @author zongf

  * @created 2019-07-18

  */

  public static Connection getConnection(boolean autoCommitTx) {

  try {

  Connection connection = dataSource.getConnection();

  connection.setAutoCommit(autoCommitTx);

  return connection;

  } catch (SQLException e) {

  e.printStackTrace();

  }

  return null;

  }

  /**

  * @Description: 获取本线程连接

  * @return: Connection 数据库连接

  * @author: zongf

  * @time: 2019-06-26 14:37:00

  * @since 1.0

  */

  public static TxConnection getTxConnection() {

  TxConnection txConnection = null;

  // 如果ThreadLocal 中连接为空, 则创建新的连接

  if (txConnectionLocal.get() == null || txConnectionLocal.get().getConnection() == null) {

  txConnection = new TxConnection(getConnection(true));

  txConnectionLocal.set(txConnection);

  } else {

  txConnection = txConnectionLocal.get();

  }

  return txConnection;

  }

  /** 获取当前线程内的数据库连接

  * @return Connection

  * @since 1.0

  * @author zongf

  * @created 2019-07-18

  */

  public static Connection getLocalConnection() {

  return getTxConnection().getConnection();

  }

  /** 获取当前线程的数据库连接对象

  * @return ThreadLocal

  * @since 1.0

  * @author zongf

  * @created 2019-07-18

  */

  public static ThreadLocal getLocalTxConnection() {

  return txConnectionLocal;

  }

  /** 当归还连接时, 需要设置自动提交事务为true.

  * @param connection

  * @return null

  * @since 1.0

  * @author zongf

  * @created 2019-07-18

  */

  public static void release(Connection connection) throws SQLException {

  try {

  if (connection != null && !connection.isClosed()) {

  connection.setAutoCommit(true);

  }

  } catch (SQLException e) {

  e.printStackTrace();

  } finally {

  connection.close();

  }

  }

  }

  2.3 定义事务连接对象

  由于在事务嵌套时, 需要遵循哪一层动态代理开启的事务, 由哪一层动态代理负责事务的开启和回滚, 因此需要记录事务的开启者. 因此笔者创建了一个TxConnneciton 对象.

  public class TxConnection {

  private Connection connection;

  private String creator;

  // 省略setter/getter 方法

  }

  3. 自定义开启事务注解

  定义一个类似于spring @Transcation 的注解, 用于开启事务.

  openNewTx 用于模拟spring七种事务传播行为之一的 Propagation.REQUIRES_NEW

  @Documented

  @Target(ElementType.METHOD)

  @Retention(RetentionPolicy.RUNTIME)

  public @interface EnableTranscation {

  // 是否开启新的事务

  boolean openNewTx() default false;

  }

  4. 动态代理处理器

  /**事务动态代理处理器

  * @since 1.0

  * @author zongf

  * @created 2019-07-18

  */

  public class TranscationHandler implements InvocationHandler {

  private Object target;

  public TranscationHandler(Object target) {

  this.target = target;

  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  // 获取当前数据库连接

  TxConnection txConnection = DbConnUtil.getTxConnection();

  // 保存老的连接对象

  TxConnection oldTxConnection = null;

  try {

  // 获取目标对象方法

  Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());

  // 看当前方法是否开启了事务

  boolean enableTx = targetMethod.isAnnotationPresent(EnableTranscation.class);

  // 如果开启事务, 则设置当前连接为手动提交事务

  if (enableTx) {

  // 获取注解信息

  EnableTranscation annotation = targetMethod.getAnnotation(EnableTranscation.class);

  // 获取是否开启新事务

  boolean openNewTx = annotation.openNewTx();

  if (!txConnection.getConnection().getAutoCommit()) { //为false, 表示已经开启了事务

  if (openNewTx) { // 如果需要开启的事务

  // 保存原数据库连接

  oldTxConnection = txConnection;

  // 获取新的连接

  txConnection = new TxConnection(DbConnUtil.getConnection(false), this.toString());

  // 替换当前线程中的数据库连接

  DbConnUtil.getLocalTxConnection().set(txConnection);

  }

  } else { // 为true, 表示未开启事务

  // 没有开启事务, 设置自动提交为false. 表示已经开始了事务

  txConnection.getConnection().setAutoCommit(false);

  txConnection.setCreator(this.toString());

  }

  }

  // 执行目标方法

  Object object = targetMethod.invoke(this.target, args);

  // 如果事务是当前handler对象创建, 那么提交事务

  if (this.toString().equals(txConnection.getCreator())) {

  txConnection.getConnection().commit();

  }

  return object;

  } catch (Exception e) {

  if (txConnection != null && this.toString().equals(txConnection.getCreator())) {

  if (txConnection.getConnection() != null && !txConnection.getConnection().isClosed()) {

  txConnection.getConnection().rollback();

  txConnection.getConnection().setAutoCommit(true);

  }

  }

  throw new RuntimeException("发生异常, 事务已回滚!", e);

  } finally {

  // 释放数据库连接

  if (txConnection != null && this.toString().equals(txConnection.getCreator())) {

  DbConnUtil.release(txConnection.getConnection());

  }

  // 如果新连接不为null, 则表示开启了新事务. 则回滚原连接

  if (oldTxConnection != null) {

  DbConnUtil.getLocalTxConnection().set(oldTxConnection);

  }

  }

  }

  }

  5. ServiceFactory 工厂

  创建Service 工厂类, 用于模拟Spring 容器. 当目标Service中包含@EnableTransaction 注解时, 创建Service 的动态代理, 否则创建Service 对象.

  /** Service工厂, 模拟spring 容器

  * @since 1.0

  * @author zongf

  * @created 2019-07-18

  */

  public class ServiceFactory {

  /** 获取Service 实例

  * @param clz Service 实现类类型

  * @return T Service 对象或动态代理对象

  * @since 1.0

  * @author zongf

  * @created 2019-07-18

  */

  public static T getService(Class clz) {

  T t = null;

  try {

  t = clz.newInstance();

  } catch (Exception e) {

  e.printStackTrace();

  throw new RuntimeException("创建对象失败");

  }

  // 判断不能是接口, 接口不能创建实现类

  if(clz.isInterface()){

  throw new RuntimeException("接口不能创建实例!");

  }

  // 是否开启动态代理

  boolean enableTx = false;

  // 遍历所有非私有方法, 如果方法有@EnableTx注解, 则说明需要创建代理

  Method[] methods = clz.getMethods();

  for (Method method : methods) {

  if (method.getAnnotation(EnableTranscation.class) != null) {

  enableTx = true;

  break;

  }

  }

  // 如果需要创建代理, 则返回代理对象

  if (enableTx) {

  return (T) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new TranscationHandler(t));

  }

  return t;

  }

  }

  6. 声明式事务测试

  测试用例, 笔者借助于之前写的BaseDao来简化基本步骤的开发.

  1.1 定义接口

  public interface IMixService {

  // 模拟正常

  void success();

  // 模拟异常操作, 事务回滚

  void error();

  void show();

  }

  6.2 定义实现类

  public class MixService implements IMixService {

  private IUserService userService = ServiceFactory.getService(UserService.class);

  private IPersonService personService = ServiceFactory.getService(PersonService.class);

  @EnableTranscation

  @Override

  public void success() {

  this.userService.save(new UserPO("user-01", "123456"));

  this.personService.save(new PersonPO("person-01", "abcdefg"));

  }无锡妇科医院 http://www.bhnnk120.com/

  @EnableTranscation

  @Override

  public void error() {

  this.userService.save(new UserPO("user-01", "123456"));

  this.personService.save(new PersonPO("person-01", "abcdefg"));

  // 模拟异常会馆

  int a = 1/0;

  }

  @Override

  public void show() {

  List userPOS = this.userService.queryAll();

  List personPOS = this.personService.queryAll();

  System.out.println("\n****** t_user: *****");

  userPOS.forEach(System.out::println);

  System.out.println("\n****** t_person: *****");

  personPOS.forEach(System.out::println);

  }

  }

  6.3 测试用例

  public class MixService implements IMixService {

  private IUserService userService = ServiceFactory.getService(UserService.class);

  private IPersonService personService = ServiceFactory.getService(PersonService.class);

  @EnableTranscation

  @Override

  public void success() {

  this.userService.save(new UserPO("user-01", "123456"));

  this.personService.save(new PersonPO("person-01", "abcdefg"));

  }

  @EnableTranscation

  @Override

  public void error() {

  this.userService.save(new UserPO("user-01", "123456"));

  this.personService.save(new PersonPO("person-01", "abcdefg"));

  // 模拟异常会馆

  int a = 1/0;

  }

  @Override

  public void show() {

  List userPOS = this.userService.queryAll();

  List personPOS = this.personService.queryAll();

  System.out.println("\n****** t_user: *****");

  userPOS.forEach(System.out::println);

  System.out.println("\n****** t_person: *****");

  personPOS.forEach(System.out::println);

  }

  }