基于反射与动态代理封装SQL实战(一):整体框架介绍

一 前言

(1)前言

最近在学习动态代理的时候,把使用动态代理封装数据库的操作当成一个练习来做。现在分享一下,也当成一个复习动态代理的机会。大家有什么好的想法、问题还是建议都可以在评论区指出。

完整源码在GitHub:https://github.com/attendent/distrubuted

(2)实现的功能

介绍一下目前完成的部分以及后面会涉及到的博客内容:

  1. 可使用@AutoDao实现实例的注入,注入的类需要是经过@DaoMapper注释的:
@AutoDao
HelloWordDao helloWordDao;
@DaoMapper
public interface HelloWordDao {}
  1. 可使用@SqlAnnotation在接口方法上进行SQL语句的书写:
@SqlAnnotation("SELECT * FROM user WHERE user_name = #{userName}")
User sayHello(@SqlParam("userName") String userName);
  1. 其返回值支持引用类型、基本类型与List。

  2. 使用@SqlParam确定参数类型

  3. 防SQL注入处理:可以区分井号符#与美元符$的区别,对于使用井号符#修饰的参数进行防SQL注入处理

  4. 加入连接池减少建立连接产生的性能消耗

  5. 使用.yml文件配置数据库

  6. 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配置简便开发者的操作

本次整体结构的讲解就到此结束,后面将继续完善其余部分的讲解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值