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注解的解析作业先做简单处理)
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()
办法中供给了署理目标办法的完结逻辑。
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句子,然后进行参数的替换
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来处理
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;
}
此刻得到的成果,现已能够获取到参数对应的值
可是此刻还要完结参数的替换,能够按照下面逻辑去处理(能够用其它思路来完结)
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一个值一个值封装到目标中去