一 前言
(1)前言
最近在学习动态代理的时候,把使用动态代理封装数据库的操作当成一个练习来做。现在分享一下,也当成一个复习动态代理的机会。大家有什么好的想法、问题还是建议都可以在评论区指出。
完整源码在GitHub:https://github.com/attendent/distrubuted
(2)实现的功能
介绍一下目前完成的部分以及后面会涉及到的博客内容:
- 可使用
@AutoDao
实现实例的注入,注入的类需要是经过@DaoMapper
注释的:
@AutoDao
HelloWordDao helloWordDao;
@DaoMapper
public interface HelloWordDao {}
- 可使用
@SqlAnnotation
在接口方法上进行SQL语句的书写:
@SqlAnnotation("SELECT * FROM user WHERE user_name = #{userName}")
User sayHello(@SqlParam("userName") String userName);
-
其返回值支持引用类型、基本类型与List。
-
使用
@SqlParam
确定参数类型 -
防SQL注入处理:可以区分井号符#与美元符$的区别,对于使用井号符#修饰的参数进行防SQL注入处理
-
加入连接池减少建立连接产生的性能消耗
-
使用.yml文件配置数据库
-
Dao层可无实现类
(3)目前效果
执行代码:
public static void main(String[] args) throws IllegalAccessException {
noImpl();
}
// 此处为接口
@AutoDao
private static HelloWordDao helloWordDao;
public static void noImpl() throws IllegalAccessException {
SqlMapper.initSql();
List<User> user = helloWordDao.sayHello("3");
System.out.println(user);
}
输出结果:
[User(userId=4, userName=3, userPhone=15521187408, userImage=null, userPassword=918b6f31077b621bdfeefbab304499af), User(userId=5, userName=3, userPhone=22, userImage=null, userPassword=22)]
(4)注意
本篇博客属于讲解性博客,只会在适当时候贴出一部分代码并进行讲解,需要看完整代码的可以移步GitHub。
二 正文
本篇博客计划先说明整体的框架,对于里面的具体实现不做太多的讲述,在后续的博客中将会逐渐补充细节。
2.1 Dao层
作为对数据库操作的封装,那么先从Dao层入手开始解析整体的结构:
@DaoMapper
public interface HelloWordDao {
@SqlAnnotation("SELECT * FROM user WHERE user_name = #{userName}")
List<User> sayHello(@SqlParam("userName") String userName);
@SqlAnnotation("INSERT INTO user (user_id) VALUES (${userId})")
int insert(@SqlParam("userId") int userId);
@SqlAnnotation("UPDATE user SET user_name = #{userName} WHERE user_id = ${userId}")
int update(@SqlParam("userName") String userName, @SqlParam("userId") int userId);
@SqlAnnotation("DELETE FROM user WHERE user_id = #{userId}")
int delete(@SqlParam("userId") int userId);
}
@DaoMapper:将注解用于标志于Dao层接口上,表明该接口是一个可以被Mapper注册器(后续提到)注册使用并进行注入的类。
@SqlAnnotation:用来写sql语句,对于该注解标志的方法,需要传入sql语句需要的参数值,并且能够返回执行sql语句后的结果。
@Param:用来对标志指定字段的字段名,该注解内的值应与与上述的sql语句中的值相对应。
2.2 静态代理
那么我们设置完Dao层后,便需要通过静态代理对包含有@SqlAnnotation注解的方法进行处理:
public class DynamicProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Annotation[] annotations = method.getAnnotations();
String sql = "";
// 遍历方法上的注解并取得sql语句
for (Annotation annotation :
annotations) {
if (annotation instanceof SqlAnnotation) {
sql = ((SqlAnnotation) annotation).value();
}
}
// 得到参数类型
Parameter[] parameters = method.getParameters();
// 得到返回值类型
Class<?> returnType = method.getReturnType();
// 使用连接池获得连接
Connection con = connectionPool.getConnection();
// 处理并执行sql语句
PreparedStatement ps = SqlUtil.sqlHandel(sql, parameters, args, con);
Object o;
// 分类操作
try {
switch (getType(sql)) {
case "SELECT":
o = selectMethod(method, returnType, ps);
break;
case "UPDATE":
o = updateMethod(returnType, ps);
break;
case "INSERT":
o = insertMethod(returnType, ps);
break;
case "DELETE":
o = deleteMethod(returnType, ps);
break;
default:
throw new ErrorException("Sql is error");
}
} finally {
// 将连接置回连接池
connectionPool.returnConnection(con);
}
// 返回操作结果
// 注意此时并没有调用invoke方法,因此可以无需实现类
return o;
}
}
这里是整个数据库封装的核心,在里面对调用方法进行sql语句的处理、编译、执行。
2.3 SqlMapper注解注释器
public class SqlMapper {
/**
* 存放目录下所有被@DaoMapper注释的类类型
*/
private static List<Class<?>> mapper = new ArrayList<>();
/**
* 存放目录下所有的类类型
*/
private static Set<Class<?>> classes;
static {
try {
// 主目录s
classes = getClasses("com.hc.hcbasic");
} catch (UnsupportedEncodingException | ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 得到实例
* @return Object对象
*/
public static Object getInstance() {
return Proxy.newProxyInstance(
SqlMapper.class.getClassLoader(),
mapper.toArray(new Class[]{}),
new DynamicProxy());
}
}
在此处通过Mapper扫描目录下的所有类,并且将其装载到mapper中,从而可以实现利用反射得到接口的实例。并且通过扫描并注册标注有@SqlMapper的类,可以使用@AutoDao进行实例的注入。
2.4 实例注入
@AutoDao
private static HelloWordDao helloWordDao;
public static void main(String[] args) throws IllegalAccessException {
SqlMapper.initSql();
List<User> user = helloWordDao.sayHello("3");
}
在经过SqlMapper进行扫描注册后,helloWordDao已经被注入实例,可以直接使用。
2.5 连接池
public class ConnectionPool {
private String jdbcDriver; // 数据库驱动
private String dbUrl; // 数据 URL
private String dbUsername; // 数据库用户名
private String dbPassword; // 数据库用户密码
private String testTable; // 测试连接是否可用的测试表名,默认没有测试表
private int initialConnections = 10; // 连接池的初始大小
private int incrementalConnections = 5;// 连接池自动增加的大小
private int maxConnections = 50; // 连接池最大的大小
private Vector<PooledConnection> connections = null;
}
通过建立连接池可以有效减少多次建立连接的性能损耗。
2.6 Yml配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/exclusive_plug?characterEncoding=utf8&useSSL=false&serverTimezone=GMT
username: root
password: admin
使用配置文件将配置与源代码分离,可以更加简便地调整配置。
三 总结
总的来说,整个系统由静态代理、动态代理、注解三部分进行操作。
使用SqlMapping注册项目中被@SqlMapper注解标志的类,并且将被注册的类以类型匹配的方式注入到由@AutoDao注解标志的字段中。
使用静态代理消除实现类,并且提供切入dao层方法的机会
使用动态代理进行数据库操作的封装
使用连接池减少多次建立连接的性能损耗
使用yml配置简便开发者的操作
本次整体结构的讲解就到此结束,后面将继续完善其余部分的讲解。