参考自:

https://blog.csdn.net/qq_28986619/article/details/94451889

数据源选型,我采用的是C3P0,下面是需要的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>cn.dzz</groupId>
<artifactId>Persist-Framework</artifactId>
<version>1.0-SNAPSHOT</version> <properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties> <dependencies> <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency> <!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency> <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency> </dependencies> </project>

c3p0-config.xml配置信息

我设有本机两套MySQL实例,一个8 一个5

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 默认配置,如果没有指定则使用这个配置 -->
<!-- <default-config>-->
<!-- <property name="user">zhanghanlun</property>-->
<!-- <property name="password">123456</property>-->
<!-- <property name="jdbcUrl">jdbc:mysql://localhost:3306/zhanghanlun</property>-->
<!-- <property name="driverClass">com.mysql.jdbc.Driver</property>-->
<!-- <property name="checkoutTimeout">30000</property>-->
<!-- <property name="idleConnectionTestPeriod">30</property>-->
<!-- <property name="initialPoolSize">3</property>-->
<!-- <property name="maxIdleTime">30</property>-->
<!-- <property name="maxPoolSize">100</property>-->
<!-- <property name="minPoolSize">2</property>-->
<!-- <property name="maxStatements">200</property>-->
<!-- </default-config>--> <!-- 命名的配置,可以通过方法调用实现 -->
<named-config name="my-info">
<property name="user">root</property>
<property name="password">123456</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3308/my-info?serverTimezone=Asia/Shanghai</property>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<!-- 如果池中数据连接不够时一次增长多少个 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">20</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">25</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
</named-config> <named-config name="dev-base">
<property name="user">root</property>
<property name="password">123456</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3307/devbase?serverTimezone=Asia/Shanghai</property>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<!-- 如果池中数据连接不够时一次增长多少个 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">20</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">25</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
</named-config>
</c3p0-config>

获取全部数据源:

package cn.dzz.persist.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader; import javax.sql.DataSource;
import java.io.File;
import java.net.URL;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map; public class DataSourceUtil { private DataSourceUtil() {}
private static Map<String, DataSource> dataSourceMap; static {
try {
dataSourceMap = new HashMap<>();
// 读取配置文件封装成文件对象
URL resource = DataSourceUtil.class.getClassLoader().getResource("c3p0-config.xml");
File f = new File(resource.getFile()); // Dom4J转换成Dom对象
SAXReader reader = new SAXReader();
Document doc = reader.read(f); // 得到节点对象根据xml配置信息读取
Element root = doc.getRootElement();
List<Element> elements = root.elements("named-config");
for (Element element : elements) {
Attribute attribute = element.attribute("name");
String value = attribute.getValue();
// 逐一创建获取
dataSourceMap.put(value, new ComboPooledDataSource(value));
}
} catch (Exception exception) {
exception.printStackTrace();
}
} public static DataSource getDataSourceByConfigName(String configName) {
return dataSourceMap.get(configName);
} // 测试
public static void main(String[] args) throws SQLException {
System.out.println(getDataSourceByConfigName("my-info").getConnection());
}
}

跨库暂时不考虑,要实现统一事务,这里要统一从线程中获取连接对象

package cn.dzz.persist.util;

import javax.sql.DataSource;
import java.sql.Connection; public class ConnectionUtil { private static DataSource dataSource;
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); static {
dataSource = DataSourceUtil.getDataSourceByConfigName("my-info");
} public static Connection getConnection() {
try {
Connection connection = threadLocal.get();
if (null == connection) {
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

用于标记声明事务类的注解

package cn.dzz.persist.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 用于标记需要事务操作的类
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TxConn {
}

JDK代理实现:

package cn.dzz.persist.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection; public class ProxyBean {
private Object target;
private Connection connection; public ProxyBean(Object target, Connection connection) {
this.target = target;
this.connection = connection;
} public Object getBean() {
Object o = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
connection.setAutoCommit(false);
method.invoke(target, args);
connection.commit();
} catch (Exception e) {
connection.rollback();
} finally {
connection.close();
}
return null;
}
});
return o;
}
}

然后通过工厂类去获取代理对象

package cn.dzz.persist.proxy;

import cn.dzz.persist.annotation.TxConn;
import cn.dzz.persist.util.ConnectionUtil; import java.sql.Connection; /**
* 目标对象工程
*/
public class TargetBeanFactory { public static Object getTargetBean(Class<?> targetClass) throws Exception {
Object t = targetClass.newInstance();
if (t.getClass().isAnnotationPresent(TxConn.class)) {
Connection connection = ConnectionUtil.getConnection();
ProxyBean proxyBean = new ProxyBean(t , connection);
return proxyBean.getBean();
}
return t;
} }

测试的业务类:

注意一些问题,就是里面的SQL执行和业务逻辑全都把异常抛出去,这样才能触发代理对象的事务

如果自己TryCatch了,直接方法调用里面自行处理异常,那代理的对象触发不到回滚就没意义了

package cn.dzz.persist.service;

import cn.dzz.persist.annotation.TxConn;
import cn.dzz.persist.util.ConnectionUtil; import java.sql.Connection;
import java.sql.PreparedStatement; @TxConn
public class TestServiceImpl implements TestService{ @Override
public void updateTest() throws Exception {
Connection connection = ConnectionUtil.getConnection();
final String sql = "UPDATE `my-info`.`school_class` SET `CLASS_NAME` = '修改1班级 - 0001' WHERE `CLASS_ID` = 1;\n";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.execute(); int a = 10 / 0; final String sql2 = "UPDATE `my-info`.`school_class` SET `CLASS_NAME` = '修改2班级 - 0002' WHERE `CLASS_ID` = 2;\n";
PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
preparedStatement2.execute();
}
}

测试事务是否有效:

import cn.dzz.persist.proxy.TargetBeanFactory;
import cn.dzz.persist.service.TestService;
import cn.dzz.persist.service.TestServiceImpl;
import cn.dzz.persist.util.JdbcUtil;
import org.junit.Test; import java.util.List;
import java.util.Map; public class TransactionTest { @Test
public void transactionTest() throws Exception {
TestService testService = (TestService)TargetBeanFactory.getTargetBean(TestServiceImpl.class);
testService.updateTest();
}
}

【JDBC】自定义事务注解实现的更多相关文章

  1. 《四 spring源码》spring的事务注解@Transactional 原理分析

    先了解什么是注解 注解 Jdk1.5新增新技术,注解.很多框架为了简化代码,都会提供有些注解.可以理解为插件,是代码级别的插件,在类的方法上写:@XXX,就是在代码上插入了一个插件. 注解不会也不能影 ...

  2. 4-9 基于Spring JDBC的事务管理(续)

    10. 基于Spring JDBC的事务管理(续) 当需要方法是事务性的,可以使用@Transactional注解,此注解可以添加在: 接口 会使得此接口的实现类的所有实现方法都是事务性的 接口中的抽 ...

  3. Spring总结——AOP、JDBC和事务的总结

    1.上一次总结了 Spring 的核心三大组件(Core,Beans,Context),今天总结的 AOP.JDBC和事务都可以看成是核心三大组件的应用. 其中 Spring 的事务管理又以 AOP ...

  4. spring事务注解

    @Transactional只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能. Spring使用声明式事务处理,默认 ...

  5. 深入Spring:自定义事务管理

    转自: http://www.jianshu.com/p/5347a462b3a5 前言 上一篇文章讲了Spring的Aop,这里讲一下Spring的事务管理,Spring的事务管理是建立在Aop的基 ...

  6. JDBC处理事务

    一.什么是事务? 在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务! 二.事务是必须满足4个条件(AC ...

  7. Spring中的@Transactional事务注解

    事务注解方式 @Transactional 当标于类前时, 标示类中所有方法都进行事物处理 , 例子: @Transactional public class TestServiceBean impl ...

  8. Spring_事务-注解代码

    applicationContext.xml <?xml version="1.0" encoding="UTF-8"?><beans xml ...

  9. SpringBoot事务注解详解

    @Transactional spring 事务注解 1.简单开启事务管理 @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:ann ...

  10. 26.SpringBoot事务注解详解

    转自:https://www.cnblogs.com/kesimin/p/9546225.html @Transactional spring 事务注解 1.简单开启事务管理 @EnableTrans ...

随机推荐

  1. 算法学习笔记(13): Manacher算法

    Manacher算法 形象的被译为马拉车算法 这个算法用于处理简单的回文字符串的问题.可以在 \(O(n)\) 的复杂度内处理出每一个位置为中心的回文串的最长长度. 为了避免出现偶数长度的回文串,导致 ...

  2. abc356

    D1.5h没做出,E0.5h做出来啦? E 有两个做法,第一个是枚举分子来计算分母对答案的贡献,另一种是枚举分母来求分子对答案的贡献. 枚举分子来计算分母对答案的贡献需要用到数论分块,所以我们讲枚举分 ...

  3. AT_agc044_c

    problem & blog 由于看到和三进制有关的操作,可以想到建造每个结点都有三个儿子的 Trie.考虑维护两种操作. 1.Salasa 舞 对于这种操作,就是把每一个节点的第一个儿子和第 ...

  4. centos 7 mysql8 安装和卸载

    cent os 7 安装 mysql 8--install-start官网下载MySQL的RPM源,地址:https://dev.mysql.com/downloads/repo/yum/ 下载这个项 ...

  5. 通俗理解GAN -- 基础认知

    Smiling & Weeping ---- 你已春风摇曳,我仍一身旧雪 1.GAN的基本思想 GAN全称对抗生成网络,顾名思义是生成模型的一种,而他的训练则是一种对抗博弈状态中的.下面我们举 ...

  6. 幻想领域图床系统V1.2正式版发布

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 幻想领域图床系统V1.2正式版发布 日期:2018-4-1 ...

  7. 剖析 Kafka 消息丢失的原因

    目录 前言 一.生产者导致消息丢失的场景 场景1:消息体太大 解决方案 : 1.减少生产者发送消息体体积 2.调整参数max.request.size 场景2:异步发送机制 解决方案 : 1.使用带回 ...

  8. [OC]一个括号新建一个类

    [OC]一个括号新建一个类 特别说明 以下代码仅仅用于说明用途,命名也不是特别规范,小朋友不要模仿哦. 前言 在iOS开发中,我们会经常用到这么一段代码: UIView *myView = [UIVi ...

  9. js-对象创建

    哥被逼得要当全栈工程师,今天练习了下各种对象的创建方式.代码较多参考了https://www.cnblogs.com/ImmortalWang/p/10517091.html 为了方便测试,整合了一个 ...

  10. RTThread 自动网卡使用问题

    最近使用 STM32 测试了一下 lwip 和 esp8266 的网络连接问题,使用 RTThread 的自动网卡时,发现不能很好的自动切换默认网卡,不能满足需求,所以自己简单的改了一下. 一.准备材 ...