总目标:使用jdbc+注解+反射+动态代理做Java应用中带事务管理的操作数据库方法
需求:
(1)在数据库中对数据的增删改操作,为了保证数据的安全,需要加事务
(2)应用中包含很多模块,而模块中一般包含最基础单表的增删改,如果每个单表的增删改方法都要写事务管理的话,代码太冗余了,可以统一处理
(3)增删改的事务管理可以扩展到一个总方法(包含多个增删改,事务本来也是针对一组SQL执行的最终结果)
1、步骤:
先看文件结构(图中红框)
1.1 新建maven项目,添加jdbc、连接池依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
1.2 配置文件及配置文件读取工具类
配置文件:jdbc.properties,数据库这里用的是MySQL,记得在URL中改自己的数据库名称
jdbc.pool.user=root
jdbc.pool.password=123456
jdbc.pool.url=jdbc:mysql://127.0.0.1:3306/数据库名称?useSSL=true
jdbc.pool.driverName=com.mysql.cj.jdbc.Driver
jdbc.pool.initialSize=3
jdbc.pool.maxActive=10
jdbc.pool.maxWait=5000
配置文件内容读取类:ConfigBuilder.java //自己加上get()方法
package com.my.zonghe.configBuild;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ConfigBuilder {
static String url;
static String user;
static String password;
static String driverName;
static Integer initialSize;
static Integer maxActive;
static Integer maxWait;
static {
//1、读取配置文件,路径从target/classes下开始
String path = ConfigBuilder.class.getClassLoader().
getResource("com/my/zonghe/config/jdbc.properties").getPath();
//2.使用properties读取内容
Properties ps = new Properties();
InputStream in = null;
try {
in = new FileInputStream(new File(path));
ps.load(in);
url = ps.getProperty("jdbc.pool.url");
user = ps.getProperty("jdbc.pool.user");
password = ps.getProperty("jdbc.pool.password");
driverName = ps.getProperty("jdbc.pool.driverName");
initialSize = Integer.valueOf(ps.getProperty("jdbc.pool.initialSize"));
maxActive = Integer.valueOf(ps.getProperty("jdbc.pool.maxActive"));
maxWait = Integer.valueOf(ps.getProperty("jdbc.pool.maxWait"));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//自己加上get()方法吧
}
1.3 某业务模块,这里以dept单表为例。由于核心代码不多,直接截图吧
简单说明:
(1)数据库表dept,这个表只有三个字段,其中deptno为主键,并且自增,其他为字符类型。以该表在entity包下建实体对象Dept。
(2)DeptDaoImpl继承了BaseConnection,实现了DeptDao,具体方法在DeptDaoImpl中写,只需要写sql串和SQL执行。事务不在这里处理,到代理类中统一处理
(3)BaseConnection定义了数据库连接,所有想使用数据库连接的,直接继承该类即可,BaseConnection中代码如下
1.4 数据库连接次、代理类等基础模块
(1)BaseDao.java 数据库连接池(数据源)
package com.my.zonghe.Base;
import org.apache.commons.dbcp.BasicDataSource;
import com.my.zonghe.configBuild.ConfigBuilder;
public class BaseDao {
//1、
private static BasicDataSource basicDataSource;
public static BasicDataSource getBasicDataSource() {
if(basicDataSource == null) {
basicDataSource = new BasicDataSource();
basicDataSource.setUsername(ConfigBuilder.getUser());
basicDataSource.setPassword(ConfigBuilder.getPassword());
basicDataSource.setUrl(ConfigBuilder.getUrl());
basicDataSource.setDriverClassName(ConfigBuilder.getDriverName());
//3、设置连接池使用配置,包括初始化数量、最大连接数、最小空闲数、最大等待时间
basicDataSource.setInitialSize(ConfigBuilder.getInitialSize());
basicDataSource.setMaxActive(ConfigBuilder.getMaxActive());
//从连接池中获取连接的最大等待时间
basicDataSource.setMaxWait(ConfigBuilder.getMaxWait());
}
return basicDataSource;
}
}
(2)BaseConnection.java 在上面已经说明
(3)DaoHandler.java代理类,对包含数据库操作方法(通过注解进行识别)加上事务管理。在代码中加了一些打印,直观看到执行
package com.my.zonghe.Base;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import com.my.zonghe.myUtil.Transaction;
public class DaoHandler implements InvocationHandler{
private Object targetObject;
public Object newProxyInstance(Object targetObject) {
this.targetObject = targetObject;
Object newProxyInstance = Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(), this);
//获得动态代理对象
return newProxyInstance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
if(method.getAnnotationsByType(Transaction.class).length > 0) {
//有注解
System.out.println("提交前");
Field field = targetObject.getClass().getSuperclass().getDeclaredField("conn");
field.setAccessible(true);
System.out.println(field.get(targetObject));
Connection conn = (Connection)field.get(targetObject);
conn.setAutoCommit(false);
try {
obj = method.invoke(targetObject, args);
conn.commit();
System.out.println("提交了");
} catch (Exception e) {
e.printStackTrace();
conn.rollback();
System.out.println("回滚了");
} finally {
if(conn != null) {
conn.close();
System.out.println("关闭连接了");
}
}
}else {
//不使用事务也得关闭连接
obj = method.invoke(targetObject, args);
Field field = targetObject.getClass().getSuperclass().getDeclaredField("conn");
if(field != null) {
field.setAccessible(true);
Connection conn = (Connection)field.get(targetObject);
if(conn != null) {
conn.close();
System.out.println("关闭连接了");
}
}
}
return obj;
}
}
(4)事务管理注解
Transaction.java,在需要事务管理的方法上加注解,如本文DeptDao.java方法(获取代理类时,用父类引用指向子类对象,加在接口DeptDao.java上即可)
package com.my.zonghe.Base;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Transaction {
public String value() default "";
}
2、测试TestDemo
代码:
package com.my.zonghe.test;
import com.my.zonghe.Base.DaoHandler;
import com.my.zonghe.dept.dao.DeptDao;
import com.my.zonghe.dept.dao.impl.DeptDaoImpl;
import com.my.zonghe.dept.entity.Dept;
public class TestDemo {
public static void main(String[] args) {
Dept dept = new Dept();
dept.setDname("asd1");
dept.setLoc("zxcvb1");
DaoHandler dh = new DaoHandler();
DeptDaoImpl ddao = (DeptDaoImpl) dh.newProxyInstance(new DeptDaoImpl());
Integer i;
try {
i = ddao.insert(dept);
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试中,可以在提交SQL的方法中,代码提交后造一个异常,触发回滚,如