MyBatis (八)—— 自定义一个小MyBatis

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/QQ2899349953/article/details/98082435

最近研究了一下Mybatis的底层代码,准备写一个操作数据库的小工具,实现了Mybatis的部分功能:

1. SQL语句在mapper.xml中配置。
2. 支持int,String,自定义数据类型的入参。
3. 根据mapper.xml动态创建接口的代理实现对象。

功能有限,目的是搞清楚MyBatis框架的底层思想,多学习研究优秀框架的实现思路,对提升自己的编码能力大有裨益。

小工具使用到的核心技术点:xml解析+反射+jdk动态代理

接下来,一步一步来实现。

首先来说为什么要使用jdk动态代理。

传统的开发方式:

  1. 接口定义业务方法。
  2. 实现类实现业务方法。
  3. 实例化实现类对象来完成业务操作。

Mybatis的方式:

  • 开发者只需要创建接口,定义业务方法。
  • 不需要创建实现类。
  • 具体的业务操作通过配置xml来完成。

MyBatis的方式省去了实现类的创建,改为用xml来定义业务方法的具体实现。

那么问题来了。

我们知道Java是面向对象的编程语言,程序在运行时执行业务方法,必须要有实例化的对象。但是,接口是不能被实例化的,而且也没有接口的实现类,那么此时这个对象从哪来呢?

程序在运行时,动态创建代理对象。

所以我们要用JDK动态代理,运行时结合接口和mapper.xml来动态创建一个代理对象,程序调用该代理对象的方法来完成业务。

动态代理参考→动态代理

如何使用jdk动态代理?

创建一个类,实现InvocationHandler接口,该类就具备了创建动态代理对象的功能,当然,既然是处理数据库相关的,自然还会用到数据库连接池(连接池这一块直接用就好了,不然工作量好大的),这里使用的是druid连接池,数据库连接池的实现可参考→自己实现一个mini版的数据库连接池

我的想法就是,直接用druid数据库连接池去连接数据库,然后主要解析的Mapper.xml文件是,因为在MyBatis里面,config.xml里面的数据主要就是给它自带的数据库连接池用的,这儿我们已经用了连接池了,那就只需要解析Mapper.xml里面的数据了,我尝试了一下,xml文件的解析还是比较麻烦的→使用SAX解析XML文件,但是我们的主要逻辑在于动态代理的实现,那这个xml文件我就拿txt文件代替了,反正它不是重点;

可能要好多天,,,,,别急,,

唉,真是气死我了,我们自己拿JDK动态代理写的时候,需要一个公共接口、一个真实对象然后通过动态代理类动态生成代理对象,但是我看了MyBatis的源码,这里面我真没看懂,我们只需要一个公共接口,因为它在代码里面需要传递的参数就这一个,那它的真实对象在哪?我看资料说,真正的执行器是一个叫Execute的接口的实现类,那它这个实现类是怎么继承我们自定义的这个接口的?难道一个已经创建好的类还能动态的继承一个接口吗?即便它动态继承了,那它还需要动态实现我们自定义接口下的自定义的方法?我的天?

看样子框架不愧叫框架,,,,,我写了一个伪MyBatis,这就当是对JDK动态代理的一种简单运用吧,我自定义实现的真实对象,下面是代码:

druid的依赖:

        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.26</version>
            <scope>compile</scope>
        </dependency>

druid的配置文件,druid.properties:

#驱动路径
driver=com.mysql.jdbc.Driver
#JDBC连接URL
url=jdbc:mysql://127.0.0.1:3306/test4?useSSL=false
#账号
username=root
#密码
password=123456
#初始连接池大小
initPoolSize=10
#最大空闲时间
maxIdleTime=20
#最大连接池数
maxPoolSize=40

数据库的创建:

create table teacher
(
  tid   int(20)     not null
    primary key,
  tname varchar(20) null
);

数据随便插几个,这是我的:
在这里插入图片描述
表对应的实体类POJO:

/**
 * @ClassName Teacher
 * @Description teacher表对应的实体类
 * @Author lzq
 * @Date 2019/8/2 14:40
 * @Version 1.0
 **/
public class Teacher {
    private int tid;
    private String tname;

    public Teacher(int tid,String tname) {
        this.tid = tid;
        this.tname = tname;
    }

    public int getTid() {
        return tid;
    }

    public void setTid(int tid) {
        this.tid = tid;
    }

    public String getTname() {
        return tname;
    }

    public void setTname(String tname) {
        this.tname = tname;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "tid=" + tid +
                ", tname='" + tname + '\'' +
                '}';
    }
}

获取数据库连接池连接的类:

import com.alibaba.druid.pool.DruidDataSource;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @ClassName ConnectionTest
 * @Description 获取数据库连接池的连接
 * @Author lzq
 * @Date 2019/8/2 14:32
 * @Version 1.0
 **/
public class ConnectionTest {
    public static Connection getConection() throws IOException, SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        //读取配置信息
        Properties pro = new Properties();
        pro.load(ConnectionTest.class.getClassLoader
                ().getResourceAsStream("druid.properties"));
        dataSource.setDriverClassName(pro.getProperty("driver"));
        dataSource.setUrl(pro.getProperty("url"));
        dataSource.setUsername(pro.getProperty("username"));
        dataSource.setPassword(pro.getProperty("password"));
        //初始化连接数量
        dataSource.setInitialSize(Integer.parseInt(pro.getProperty("initPoolSize")));
        //配置连接等待超时时间
        dataSource.setMaxWait(Long.parseLong(pro.getProperty("maxIdleTime")));
        //最大并发连接数
        dataSource.setMaxActive(Integer.parseInt(pro.getProperty("maxPoolSize")));

        Connection connection = dataSource.getConnection();
        return connection;
    }
}

读取SQL语句文件的类:

/**
 * @ClassName InputSql
 * @Description 读取SQL语句
 * @Author lzq
 * @Date 2019/8/2 14:19
 * @Version 1.0
 **/
public class InputSql {
    public static String getSql(String path) {
        File file = new File(path);
        StringBuilder stringBuilder = new StringBuilder();
        try {
            Scanner scanner = new Scanner(file);
            String temp = null;
            while (scanner.hasNextLine()) {
                temp = scanner.nextLine();
               // System.out.println(temp);
                stringBuilder.append(temp);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return stringBuilder.toString();
    }
}

公共接口:

/**
 * @ClassName TeacherMapper
 * @Description 各个接口
 * @Author lzq
 * @Date 2019/8/2 14:39
 * @Version 1.0
 **/
public interface TeacherMapper {
    public Teacher getTeacherId(int id);
}

执行器,真实对象:

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @ClassName Execute
 * @Description 执行器,真实对象
 * @Author lzq
 * @Date 2019/8/2 14:41
 * @Version 1.0
 **/
public class Execute implements TeacherMapper{
    private String path = "";

    public Execute(String path) {
        this.path = path;
    }

    @Override
    public Teacher getTeacherId(int id) {
        Connection conection = null;
        Teacher teacher = null;
        try {
            conection = ConnectionTest.getConection();
            String sql = InputSql.getSql(this.path)+"?";
            PreparedStatement preparedStatement = conection.prepareStatement(sql);
            preparedStatement.setObject(1,id);
            ResultSet resultSet = preparedStatement.executeQuery();

            while (resultSet.next()) {
                teacher = new Teacher(resultSet.getInt(1), resultSet.getString(2));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if(conection != null) {
                try {
                    conection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return teacher;
    }
}

生成代理对象,并让真实对象执行相应方法:

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

/**
 * @ClassName ProxyExample
 * @Description 生成代理对象
 * @Author lzq
 * @Date 2019/8/2 15:17
 * @Version 1.0
 **/
public class ProxyExample implements InvocationHandler {
    private Object object = null;  //真实对象

    public Object bind(Object o) {
        this.object = o;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),o.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object o = method.invoke(object,args);
        return o;
    }
}

测试类:

我的存放SQL语句的文件:
在这里插入图片描述

/**
 * @ClassName MyTest
 * @Description 测试类
 * @Author lzq
 * @Date 2019/8/2 17:34
 * @Version 1.0
 **/
public class MyTest {
    public static void main(String[] args) {
        ProxyExample proxyExample = new ProxyExample();
        TeacherMapper teacherMapper = (TeacherMapper)proxyExample.bind(new Execute("E:\\FHB\\io\\sql.txt"));
        Teacher teacherId = teacherMapper.getTeacherId(2);
        System.out.println(teacherId);
    }
}

运行结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/QQ2899349953/article/details/98082435