Mybatis源码初探
之前都只是在用Mybatis,但是一直没有深入的去了解过它的源码机制,趁着最近空闲时间比较多,再加上日常生活中用这框架比较多,就来简单探究下它的源码
Mybatis的简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。(来自中文官网介绍)
JDBC核心步骤
Mybatis、Hibernate、DATA JPA 都是基于JDBC驱动来做的,那么我们来看看JDBC的几个核心步骤是怎么做的?
- 加载驱动
- 获取连接
- 创建sql语句
- 获取statement对象
- 执行sql语句
- 处理结果集
- 关闭连接
Mybatis核心步骤
Mybatis的基本操作如何做的?
- 创建xml配置文件
- 创建mapper接口
- 需要SqlSessionFactory对象
- 获取SqlSession对象
此时我们通过最简单的方法来模拟Mybatis是如何解析sql最后查询出数据的
先给定一个mapper接口 (此处不使用xml形式和@Param注解的解析工作先做简单处理)
java
public interface EmpMapper {
@Select("select * from emp where empno = #{empno} and ename = #{ename}")
List<Emp> selectEmpList( Integer empno, String ename);
}
大家都知道,此时想调用selectEmpList方法,需要具体的实例对象,但是接口不能实例化**(所以此时我们需要动态代理,因为是接口,则使用JDK的动态代理)**
Proxy.newProxyInstance()
方法创建了一个代理对象 empMapper
,该代理对象实现了 EmpMapper
接口,并在 InvocationHandler
的 invoke()
方法中提供了代理对象方法的实现逻辑。
java
EmpMapper empMapper = (EmpMapper) Proxy.newProxyInstance(MyMybatis.class.getClassLoader(), new Class[]{EmpMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println(method.getName());
// System.out.println(Arrays.asList(args));
return null;
}
});
empMapper.selectEmpList(7369,"SMITH");
}
这里的method就是接口方法的名称,args就是传入的参数
但是此时想要查询语句,得要先获取到@Select注解上的sql语句,然后进行参数的替换
java
EmpMapper empMapper = (EmpMapper) Proxy.newProxyInstance(MyMybatis.class.getClassLoader(), new Class[]{EmpMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法上的注解
Select select = method.getAnnotation(Select.class);
if (select!=null){
//核心逻辑处理(获得sql语句)
String[] value = select.value();
String sql = value[0];
System.out.println(sql);
}
return null;
}
});
empMapper.selectEmpList(7369,"SMITH");
}
此时的sql语句是
我们已经得到了sql语句,现在要获取参数的替换,但是由于args是个数组,我们也不知道如何跟下面的参数进行匹配,可以用map来处理
java
EmpMapper empMapper = (EmpMapper) Proxy.newProxyInstance(MyMybatis.class.getClassLoader(), new Class[]{EmpMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过参数来完成替换功能,因此需要先去解析参数
Map<String, Object> stringObjectMap = parseArgs(method, args);
System.out.println(stringObjectMap);
// 获取方法上的注解
Select select = method.getAnnotation(Select.class);
if (select!=null){
//核心逻辑处理(获得sql语句)
String[] value = select.value();
String sql = value[0];
System.out.println(sql);
}
return null;
}
});
empMapper.selectEmpList(7369,"SMITH");
}
public static Map<String,Object> parseArgs(Method method,Object[] args){
Map<String,Object> map = new HashMap<String,Object>();
// 获取方法的参数数组
Parameter[] parameters = method.getParameters();
int index[] = {0};
// 遍历获取所有参数名称
Arrays.asList(parameters).forEach(parameter -> {
String name = parameter.getName();
// 参数名和参数值做一一匹配
map.put(name,args[index[0]]);
// 修改下标
index[0]++;
});
return map;
}
此时得到的结果,已经可以获取到参数对应的值
但是此时还要完成参数的替换,可以按照下面逻辑去处理(可以用其它思路来实现)
java
public static String parseSql(String sql,Map<String,Object> map){
// 作为字符追加
StringBuilder builder = new StringBuilder();
for(int i = 0;i<sql.length();i++){
// 转为字符
char c = sql.charAt(i);
// 边界判断
if (c == '#'){
// 找到# 下一个位置 {
int index = i+1;
// 取出 {
char nextChar = sql.charAt(index);
// 边界判断
if (nextChar!='{'){
throw new RuntimeException("sql语句有错误,不是以{开头的");
}
StringBuilder argBuilder = new StringBuilder();
// 此方法为解析{}里面的内容
i = parseSqlArgs(argBuilder,sql,index);
// 第一次循环完成 此时argName 应该是 empno
String argName = argBuilder.toString();
// 从map中取出key为empno的值
Object value = map.get(argName);
// 追加操作
builder.append(value.toString());
continue;
}
// 不符合#的字符 做追加操作
builder.append(c);
}
return builder.toString();
}
public static int parseSqlArgs(StringBuilder argBuilder,String sql,int index){
index++;
for (;index<sql.length();index++){
// 取出索引位置的字符 此时第一次循环应该为{empno}中的 e
char c = sql.charAt(index);
// 边界判断
if (c!='}'){
// 字符追加
argBuilder.append(c);
// 继续
continue;
}
if(c=='}'){
return index;
}
}
throw new RuntimeException("sql语句错误,没有以}结尾");
}
此时已经可以看到打印出来的sql语句
紧接着就可以做以下操作了 (这也是Mybatis的核心处理操作)
- 数据库的操作(jdbc的解析)
- 结果集的映射处理
- ResultSet一个值一个值封装到对象中去