今天开始进行mybatis的源码学习
第一阶段,通过手写mybatis执行过程复习一下mybatis核心知识点
第二阶段,mybatis配置源码学习
第三阶段,mybatis核心执行流程源码学习
第四阶段,mybatis核心接口和类源码学习
第五阶段,mybatis包以及跟spring整合源码学习
先来回顾一下mybatis的使用方法吧。一个最简单的使用mybatis进行orm的程序肯定有以下几个要素
一、实体类
二、实体对应的表结构
三、mybatis配置文件
四、mybatis接口
五、mapper.xml
最简单的使用方式,在接口的方法上添加@Select注解并编写sql文件,mybatis解析该注解并进行数据库交互
问题来了,如果我们想自己实现这个过程,应该怎么做?
首先要明确我们的最终目的,就是和数据库建立连接,然后执行这个sql并获取返回值,所以第一步,我们要先解析这个sql。然而这是一个接口,无法直接调用,在mybatis中,是使用动态代理的方式来实现的,我们也可以来模仿一下
public static void main (String[] args) {
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 {
return null;
}
});
empMapper.selectEmpList(7369, "SMITH");
}
如此通过代理类就可以获取接口方法和参数
接下来就是解析sql,将sql中的占位符替换成方法中的参数
解析方法中的参数并存入map
获取方法上的注解,解析sql并替换参数
parseSql方法将sql中的占位符换成方法参数值
如果出现解析方法参数存入的键是arg0,arg1的情况是,需要在idea的Java Compiler中添加-parameters参数
parseSqlArgs
到这sql语句就有了,后面就要执行数据库操作和结果集的映射处理
上面只是mybatis最基本的功能之一,我想说的是,按照这样的思路,我们完全可以自己写一个简化版的mybatis,用自己的方式实现mybatis的功能,比如现在我们实现了selec sql的解析,那能不能再实现update?delete?答案是肯定的。所以mybatis原理并不难,简单来说有下面几部分
逐个回顾一下,配置文件里是一些最基本的配置,比如数据库连接基本属性,连接池,environment等。配置文件里有个mappers标签,指向的是接口类或mapper.xml文件等mapper中心。在jdbc中sql都存放在Statement里,这个类属于java.sql,而在mybatis中对应的则是StatementHandler。执行sql就要用到执行器,接收则是ResultSetHandler
完整代码
package com.th;
import org.apache.ibatis.annotations.Select;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class MyMybatis {
public static void main (String[] args) {
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);
// 获取方法上的注解
Select select = method.getAnnotation(Select.class);
if (select != null) {
//核心逻辑处理
String[] value = select.value();
String sql = value[0];
// 解析sql并替换参数
sql = parseSql(sql, stringObjectMap);
System.out.println(sql);
}
// 数据库操作
// jdbc解析
// 结果集的映射处理
// ResultSet一个值一个值封装到对象中去
return null;
}
});
empMapper.selectEmpList(7369, "田皓");
}
/**
* 解析sql
* @param sql
* @param map
* @return
*/
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);
String argName = argBuilder.toString();
Object value = map.get(argName);
builder.append(value.toString());
continue;
}
builder.append(c);
}
return builder.toString();
}
/**
* 解析并截取sql占位符中的参数
* @param argBuilder
* @param sql
* @param index
* @return
*/
public static int parseSqlArgs (StringBuilder argBuilder, String sql, int index) {
index++;
for (; index < sql.length(); index++) {
char c = sql.charAt(index);
if (c != '}') {
argBuilder.append(c);
continue;
}
if (c == '}') {
return index;
}
}
throw new RuntimeException("sql语句错误,没有以}结尾");
}
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;
}
}