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

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

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

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

配置连接池

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

        
            com.alibaba
            druid
            1.1.17
        

然后在我们的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;



public class MysqlUtil {

    private static DataSource dataSource;

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

    
    public void parseConfig(String configName) throws Exception {
        Properties properties = new Properties();
        properties.load(MysqlUtil.class.getClassLoader().getResourceAsStream(configName));
        dataSource = DruidDataSourceFactory.createDataSource(properties);
    }

    
    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;



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();
         
        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;



public class MysqlUtil {

    private static DataSource dataSource;

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

    
    public void parseConfig(String configName) throws Exception {
        Properties properties = new Properties();
        properties.load(MysqlUtil.class.getClassLoader().getResourceAsStream(configName));
        dataSource = DruidDataSourceFactory.createDataSource(properties);
    }

    
    public Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    public  T getMapper(Class clazz){

        
        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;



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

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

    
    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;


public interface UserDao {

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

}

User:

package entity;

import utils.DataName;



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层和实体类的代码,以及最后的效果,是不是有那么一点点点使用框架的味道了?

当然,整个项目不论是结构还是使用逻辑都还是比较粗糙的,下一篇会使用工厂设计模式把项目整体的重构一下。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5706764.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存