从JDBC到手撸极简版Mybaties(4)连接池加动态代理

简介

好了,其实就目前来说,我们的JDBC类已经封装的差不多了。但是!别忘了我们最初的目的!手写极简版的小框架!!
所以对于目前来说,我们的数据库连接的管理(即:啥时候连接,啥时候断开,连接时长等)在我们的工具类中还是很混乱的,而站在巨人的肩膀上才能看的更远!
SO,我们选择使用连接池进行自动管理。当然,市面上连接池有不少,我们选用来自阿里的德鲁伊(druid)来进行管理。别问为啥,我就是觉得这个名字挺不错的,想用别的也行,没啥大区别。
之后,我们还需要加入动态代理,来让我们的项目更偏向于一个”框架“。

配置连接池

我们的项目使用的是Mevn进行管理,所以嘛,,在pom.xml中加入包就可以啦:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>

然后在我们的resources目录下创建配置文件,文件名以.properties结尾:

driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test
#&useSSL=false
username=root
password=123456

# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000

好了,基本配置完成,我们先来使用下叭:

Properties properties = new Properties();
properties.load(MysqlUtil.class.getClassLoader().getResourceAsStream(configName));
dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("select * from User");
ResultSet resultSet = preparedStatement.executeQuery();

(configName是你的文件名,上篇里面有详细解释)
可以看出,德鲁伊这个包采用的是工厂设计模式,嘿嘿,这次我们先不用这种设计模式,后面再慢慢引入。
芜湖~真是太方便了。。。
接下来,我们把它好好的封装起来

工具封装

这里,我新建一个MysqlUtil的类,在里面进行德鲁伊的封装:

package utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @author :byMJC
 * @date :Created 2022/1/16 19:38
 * @description:
 */

public class MysqlUtil {

    private static DataSource dataSource;

    public MysqlUtil(String s) {
        try {
            parseConfig(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析配置文件
     * @param configName 文件名
     * @throws IOException
     */
    public void parseConfig(String configName) throws Exception {
        Properties properties = new Properties();
        properties.load(MysqlUtil.class.getClassLoader().getResourceAsStream(configName));
        dataSource = DruidDataSourceFactory.createDataSource(properties);
    }

    /**
     * 获取一个连接
     * @return
     */
    public Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }
}


这样,我们使用

MysqlUtil mysqlUtil = new MysqlUtil("JDBCConfig.properties");
mysqlUtil.getConnection();

就可以拿到我们的连接了。

java动态代理

一般动态代理有俩种,java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
我们选用jva动态代理来实现!
具体步骤:

  • 通过实现 InvocationHandler 接口创建自己的调用处理器
  • 通过为 Proxy 类指定 ClassLoader对象和一组interface来创建动态代理类
  • 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
    好了,加下来我们来实现下试试叭:
    首先创建一个User接口和一个User的实现类在entity包下:
    在这里插入图片描述
    代码内容嘛,就定义了一个say函数,打印了点东西:
    在这里插入图片描述
    然后,再handler包下创建一个UserInvocationHandler类,实现InvocationHandler接口(即上面介绍的第一步,创建自己的处理器类):
package com.sy.handler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author :byMJC
 * @date :Created 2022/1/17 0:23
 * @description:
 */

public class UserInvocationHandler implements InvocationHandler {
    // 用于接收传进来的要被代理的对象
    private Object object;

    public UserInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        System.out.println("代理:执行方法前!");
        // 执行函数
        method.invoke(object,objects);
        System.out.println("代理:方法执行后!");
        return null;
    }
}

如果method.invoke(object,objects);看不懂的化,可以再去看看反射。
,随后,我们就可以在主函数内实现一个简单的动态代理啦:

    public static void main(String[] args) {
    	// 明明是动态代理了,为啥还要新建一个实体类类?
    	//只是方便拿这个接口的方法罢了,如果传入的方法不对,后面的强转会报错的。
    	//还有就是调用方法需要实体类,如果使用反射创建的话,
    	//,这个"简单的例子"就不那么"简单"了,哈哈
        User user = new UserImpl();
         /**
         * 参数一:一个classLoader的对象,定义了由哪个classloader对象生成的代理类进行加载
         * 参数二:一个接口数组,表示我们的代理对象有哪些接口
         * 参数三:一个InbocationHander对象,表示动态代理对象会关联到哪个InvocationHander对象上,并有它调用。
         * 返回值:返回代理对象
         */
        User user1 = (User) Proxy.newProxyInstance(demo.class.getClassLoader(),
                user.getClass().getInterfaces(), new UserInvocationHandler(user));
       user1.say();
    }

运行结果:
在这里插入图片描述
ok,ok,现在为止,你的动态代理就算会了,接下来我们来结合反射和注解,来融入我们的实战中叭!

加入动态代理

由于现在的项目有点点小大了,如果按顺序说反而更复杂,不如直接给大家看总体的比较直观。

项目结构

我们创建一个实体类User用于接收查询的数据,创建一个UserDao来进行对数据库User的查找(这里只列举查找)。
在这里插入图片描述
然后新建一个utils包,里面放我们的德鲁伊工具类和我们的SqlInvocationHandler类(sql语句的处理器,以及俩自己写的注解,这俩注解主要是后面用于存放sql语句,返回类型,数据库映射等信息):
在这里插入图片描述
当然,还有我们的主函数在demo中!

MysqlUtil实现

MysqlUtil主要是前面对德鲁伊的封装,现在再加上创建动态代理的函数,整体代码如下:

package utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @author :byMJC
 * @date :Created 2022/1/16 19:38
 * @description:
 */

public class MysqlUtil {

    private static DataSource dataSource;

    public MysqlUtil(String s) {
        try {
            parseConfig(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析配置文件
     * @param configName 文件名
     * @throws IOException
     */
    public void parseConfig(String configName) throws Exception {
        Properties properties = new Properties();
        properties.load(MysqlUtil.class.getClassLoader().getResourceAsStream(configName));
        dataSource = DruidDataSourceFactory.createDataSource(properties);
    }

    /**
     * 获取一个连接
     * @return
     */
    public Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    public <T> T getMapper(Class clazz){

        /**
         * 参数一:一个classLoader的对象,定义了由哪个classloader对象生成的代理类进行加载
         * 参数二:一个接口数组,表示我们的代理对象有哪些接口
         * 参数三:一个InbocationHander对象,表示动态代理对象会关联到哪个InvocationHander对象上,并有它调用。
         * 返回值:返回代理对象
         */
        T t =  (T)Proxy.newProxyInstance(MysqlUtil.class.getClassLoader(),
               new Class[] {clazz}, new SqlInvocationHandler(getConnection(),clazz));
        return t;
    }
}

这里我们可以看到,我们创建代理的函数有一个传入参数,是一个Class,它就是我们要被代理的对象的类型,嘿嘿,因为前面的例子为了更直观和简单,所以没有用反射,但是现在实战了。
而我们的SqlInvocationHandler的构造函数也需要connecion来进行数据库操作,需要被代理对象的Class后面用于获取对应方法

DataName注解和SqlString注解的实现

DataName注解主要用于实体类对象对数据库的映射,因为为了防止sql注入,实体类的成员的名字在代码中和数据库中往往不会取一模一样的。

// 注解类型是使用在成员上,生效时间是运行时
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataName {
    String value();
}

,SqlString注解用于存放sql语句,sql数据类型和返回数据类型:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SqlString {
    String sql();
    String type();
    String resType();
}

SqlInvocationHandler类实现

这里面主要是查询数据,然后利用反射来返回数据了。

package utils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

/**
 * @author :byMJC
 * @date :Created 2022/1/16 21:21
 * @description:
 */

public class SqlInvocationHandler  implements InvocationHandler {
    public Connection connection;
    public Class aClass;

    public SqlInvocationHandler(Connection connection,Class clazz) {
        this.connection = connection;
        this.aClass = clazz;
    }

    /**
     * 获取这个类的方法的注解
     * @param method
     * @return
     */
    public SqlString getSqlString(Method method){
        Method[] declaredMethods = aClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            if (declaredMethod.getName().equals(method.getName())){
                return declaredMethod.getAnnotation(SqlString.class);
            }
        }
        System.out.println("注解出错!");
        return null;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        List resList = new ArrayList();
        // 获取该方法的注解
        SqlString sqlString = getSqlString(method);
        if(sqlString.type().equals("select")){
            // 利用反射获取返回类型的class
            Class<?> tClass = Class.forName(sqlString.resType());
            Field[] declaredFields = tClass.getDeclaredFields();

            // 创建语句
            PreparedStatement preparedStatement = connection.prepareStatement(sqlString.sql());
            // 赋值参数
            for (int i = 0; i < objects.length; i++) {
                preparedStatement.setObject(i+1, objects[i]);
               // System.out.println(objects[i]);
            }
            // 执行语句
            ResultSet resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                // 反射构建类
                Object res = tClass.getDeclaredConstructor().newInstance();
                for (Field declaredField : declaredFields) {
                    Object object = resultSet.getObject(declaredField.getAnnotation(DataName.class).value());
                    declaredField.setAccessible(true);
                    declaredField.set(res, object);
                }
                resList.add(res);
            }
            preparedStatement.close();
            resultSet.close();
            return resList;
        }


        return null;
    }
}

总体思路就是利用反射获取我们的注解的数据,然后据此进行sql类型匹配。后面就和我们前面几篇用反射封装JDBC的代码差不多了。

代码运行

最后附上我们的UserDao、User、和数据库的测试内容
UserDao:

package dao;

import entity.User;
import utils.SqlString;

import java.util.List;

/**
 * @author :byMJC
 * @date :Created 2022/1/16 21:01
 * @description:
 */
public interface UserDao {

    @SqlString(sql = "select * from user where user_id = ?", type = "select",resType = "entity.User")
    List<User> getUserById(Integer id);

}

User:

package entity;

import utils.DataName;

/**
 * @author :byMJC
 * @date :Created 2022/1/16 19:36
 * @description:
 */

public class User {

    @DataName("user_id")
    private int id;

    @DataName("user_name")
    private String userName;

    @DataName("user_pwd")
    private String userPwd;

    @DataName("user_Realname")
    private String userRealname;

    @DataName("user_tel")
    private String userTel;


    public User() {
    }

    public User(int id, String userName, String userPwd, String userRealname, String userTel) {
        this.id = id;
        this.userName = userName;
        this.userPwd = userPwd;
        this.userRealname = userRealname;
        this.userTel = userTel;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    public String getUserRealname() {
        return userRealname;
    }

    public void setUserRealname(String userRealname) {
        this.userRealname = userRealname;
    }

    public String getUserTel() {
        return userTel;
    }

    public void setUserTel(String userTel) {
        this.userTel = userTel;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", userPwd='" + userPwd + '\'' +
                ", userRealname='" + userRealname + '\'' +
                ", userTel='" + userTel + '\'' +
                '}';
    }
}

数据库数据:
在这里插入图片描述
嘿嘿:主函数:

    public static void main(String[] args) throws Exception {
        MysqlUtil mysqlUtil = new MysqlUtil("JDBCConfig.properties");
        UserDao Proxy = mysqlUtil.getMapper(UserDao.class);
        List u = (List)Proxy.getUserById(1);
        for (Object o : u) {
            System.out.println(o);
        }
    }

运行结果:
在这里插入图片描述
嘿嘿,怎么样,看着我们的Dao层和实体类的代码,以及最后的效果,是不是有那么一点点点使用框架的味道了?
在这里插入图片描述
当然,整个项目不论是结构还是使用逻辑都还是比较粗糙的,下一篇会使用工厂设计模式把项目整体的重构一下。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值