最近研究了一下Mybatis的底层代码,准备写一个操作数据库的小工具,实现了Mybatis的部分功能:
1. SQL语句在mapper.xml中配置。
2. 支持int,String,自定义数据类型的入参。
3. 根据mapper.xml动态创建接口的代理实现对象。
功能有限,目的是搞清楚MyBatis框架的底层思想,多学习研究优秀框架的实现思路,对提升自己的编码能力大有裨益。
小工具使用到的核心技术点:xml解析+反射+jdk动态代理
接下来,一步一步来实现。
首先来说为什么要使用jdk动态代理。
传统的开发方式:
- 接口定义业务方法。
- 实现类实现业务方法。
- 实例化实现类对象来完成业务操作。
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);
}
}
运行结果: