背景

项目需要,我们需要自己做一套mybatis,或者使用大部分mybatis的原始内容。对其改造,以适应需要。这就要求我再次学习一下mybatis,对它有更深入的了解。

是什么
    MyBatis是一个持久层框架,用来处理对象关系映射。说白了就是以相对面向对象的方式来提交sql语句给jdbc。如果想找个简单、快速上手的例子,最好是和spring想结合的。直接用官网的吧,简单清晰也没谁了:http://mybatis.org/spring/getting-started.html

https://mybatis.org/mybatis-3/getting-started.html

为什么

Java开发都是面向对象的思维,如果用传统下面自己去调用连接拼装sql的方式,维护成本高,代码可读性差。

public static void main(String[] args) {
//数据库连接对象
Connection conn = null;
//数据库操作对象
PreparedStatement stmt = null;
//1、加载驱动程序
try {
Class.forName(DBDRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//2、连接数据库
//通过连接管理器连接数据库
try {
//在连接的时候直接输入用户名和密码才可以连接
conn = DriverManager.getConnection(DBURL, USERNAME, PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
}
//3、向数据库中插入一条数据
String sql = "INSERT INTO person(name,age) VALUES (?,?)";
try {
stmt = conn.prepareStatement(sql);
stmt.setString(1,"陈昆仑");
stmt.setInt(2,21);
stmt.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
//4、执行语句
try {
ResultSet resultSet = stmt.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
//5、关闭操作,步骤相反哈~
try {
stmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}

怎么做

我们来看一下底层是怎么处理和交互的。基本流程如下:

看着头大?没事,我们先从最简化的版本开始添枝加叶。MyBatis可以用配置文件或者注解形式注入sql。因为配置文件方式可以方便的处理动态SQL(动态SQL就是sql语句里有if else for这些的,可以根据参数的变化最终sql也跟着变化)等优点,用的更为普遍。

假设现在是2000年,Clinton Begin还没有发起ibatis(mybatis的前身)项目。而apache基金会内部发起了讨论要设计这样一个产品,指派你作为项目负责人。现在思考,你的思路是什么?

一般思路是先把架构搭建起来,做成一个MVP最小可行性版本,然后再做功能增强。

从功能最简化方面来看,需要两步:第一步要将sql及所需要的元素以对象的形式输入,第二步是获取到这些信息转换成jdbc信息处理。

这样拆解后的思路是将sql及所需要的元素拆解成类方法的参数形式,方法本身要做的事情就是将这些参数以jdbc编程需要的形式传给jdbc执行。这里方法内部做的事情是一样的,那就自然而然的想到不用每个类都有一个实现。只要定义好接口,把实现用代理或者上层切面的方式统一处理就可以了。

根据这个思路,首先要用代理来获取参数。我设计使用方式是Insert、Select等注解里写sql元语句。通过方法参数注入参数。最终返回结果。如下

public interface UserMapper {
    @Insert("INSERT INTO person(name,age) VALUES (#{name},#{age})")
Integer insertUser(User user);
}

要实现接口的解析。先建立一个类,里面构造一个代理类,实现类似于SqlSession,所以起名叫YunaSession(yuna是我给经典java学习场景工程https://github.com/xiexiaojing/yuna 起的名字)

public class YunaSession {
public static Object dealSql(Class clazz) {
Class c[] = new Class[]{clazz};

return Proxy.newProxyInstance(YunaSession.class.getClassLoader(), c,

new YunaInvocationHandler());

   }
}

下面要实现的是代理中YunaInvocationHandler真正要实现的逻辑:将这些参数以jdbc编程需要的形式传给jdbc执行。也就是说把上面【为什么】部分一开始的那段执行jdbc的代码贴进去,将sql和参数的部分做替换。

我们把关键再贴一遍便于说明问题

//3、向数据库中插入一条数据
String sql = "INSERT INTO person(name,age) VALUES (?,?)";
try {
stmt = conn.prepareStatement(sql);
stmt.setString(1,"陈昆仑");
stmt.setInt(2,21);
stmt.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}

这里有两个?,而jdbc的预处理语句传入参数的时候要明确的知道第一个参数的类型是什么,如果传过来是对象的话,要知道对应对象的哪个值。这就是为什么接口里的预处理语句传入是

INSERT INTO person(name,age) VALUES (#{name},#{age})

因为可以通过匹配#{XX}这样的确定都是哪些参数,因为User对象里有定义参数的类型。所以类型和值都确定了。这个就是MappedStatement对象做的事情。以下是用正则表达式匹配+反射来达到解析sql并和对象值做匹配的实现:

public static void main(String[] args) throws Exception{
Matcher m= pattern.matcher("INSERT INTO person(name,age) VALUES (#{name},#{age})");
User user1 = new User();
user1.setId(1);
user1.setName("贾元春");
user1.setAge(27);
int i=1;
while(m.find()) {
System.out.println(m.group());
String group = m.group();
String fieldName = group.replace("#{","").replace("}","");
Field field = User.class.getDeclaredField(fieldName);
field.setAccessible(true);
if("java.lang.Integer".equals(field.getType().getName())) {
System.out.println("stmt.setInt("+i+","+field.get(user1)+")");
} else if("java.lang.String".equals(field.getType().getName())) {
System.out.println(" stmt.setString("+i+","+field.get(user1)+")");
}
i++;
}
}

运行结果是

可以看到实现了效果。下面就是和jdbc连接结合起来。

public class YunaInvocationHandler implements InvocationHandler {
public static final String DBDRIVER = "org.xx.mm.mysql.Driver";
public static final String DBURL = "jdbc:mysql://localhost:3306/mydb";
//现在使用的是mysql数据库,是直接连接的,所以此处必须有用户名和密码
public static final String USERNAME = "root";
public static final String PASSWORD = "mysqladmin";
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
Object result = null;
Insert insert = method.getAnnotation(Insert.class);
if (insert != null) {
String sql = insert.value()[0];
System.out.println("插入语句为"+s);
YunaSqlDeal yunaSqlDeal = new YunaSqlDeal();
yunaSqlDeal.insert(s, Arrays.toString(args));
//1、加载驱动程序
try {
Class.forName(DBDRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//2、连接数据库
//通过连接管理器连接数据库
//数据库连接对象
Connection conn = null;
try {
//在连接的时候直接输入用户名和密码才可以连接
conn = DriverManager.getConnection(DBURL, USERNAME, PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
}
composeStatement(sql, args[0], conn);
}
return 1;
}
private static final String PATTERN = "#\\{[A-Za-z0-9]+\\}";
private static Pattern pattern = Pattern.compile("("+PATTERN+")");
public static void composeStatement(String sql, Object obj, Connection conn) throws Exception{
PreparedStatement stmt = conn.prepareStatement(sql.replaceAll(PATTERN, ""));
Matcher m= pattern.matcher(sql);
int i=1;
while(m.find()) {
System.out.println(m.group());
String group = m.group();
String fieldName = group.replace("#{","").replace("}","");
Field field = User.class.getDeclaredField(fieldName);
field.setAccessible(true);
if("java.lang.Integer".equals(field.getType().getName())) {
System.out.println("stmt.setInt("+i+","+field.get(obj)+")");
stmt.setInt(i, Integer.parseInt(field.get(obj).toString()));
} else if("java.lang.String".equals(field.getType().getName())) {
stmt.setString(i, field.get(obj).toString());
}
i++;
}
stmt.execute();
stmt.close();
conn.close();
}
}

这个实现的是insert的,返回值类型固定,如果是select查询语句,涉及到返回的结果封装成对象。思路也是通过反射,和参数转换步骤差不多,就不贴代码了。

到此,我们实现了一个简化版的mybatis框架。比贴的架构图简化在少用了很多设计模式的东西,和出于性能考虑重用的东西。mybatis的核心就实现完了。

总结

本文从mybatis的设计者角度出发,构造了一个简化的mybatis框架。具体可运行的完整代码放到了我的github上,地址:

https://github.com/xiexiaojing/yuna。

很多原理性的东西看过之后会忘,但是如果真正站在设计者角度实现过一个简化的版本,相信会增强记忆。同时也能和真正的实现做对比,更深层学习技术大牛们的设计精华

推荐阅读

技术方案设计的方法
架构-稳定性建设逻辑问题实战总结
代码荣辱观-以运用风格为荣,以随意编码为耻
技术境界的二三四

mybatis的本质和原理的更多相关文章

  1. mybatis笔记3 一些原理的理解

    1,mybatis流程跟踪,原理理解 基本思路: 从SqlSessionFactory的初始化出发,观察资源的准备和环境的准备,以及实现持久层的一些过程: 进入SqlSessionFactoryBea ...

  2. atitit.查看预编译sql问号 本质and原理and查看原生sql语句

    atitit.查看预编译sql问号 本质and原理and查看原生sql语句 1. 预编译原理. 1 2. preparedStatement 有三大优点: 1 3. How to look  gene ...

  3. Mybatis拦截器实现原理深度分析

    1.拦截器简介 拦截器可以说使我们平时开发经常用到的技术了,Spring AOP.Mybatis自定义插件原理都是基于拦截器实现的,而拦截器又是以动态代理为基础实现的,每个框架对拦截器的实现不完全相同 ...

  4. mybatis入门-mapper代理原理

    原始dao层开发 在我们用mybatis开发了第一个小程序后,相信大家对于dao层的开发其实已经有了一个大概的思路了.其他的配置不用变,将原来的test方法,该为dao的方法,将原来的返回值,直接在d ...

  5. 深度分析:mybatis的底层实现原理,看完你学会了吗?

    前言 最近在和粉丝聊天的时候被粉丝问到jdbc和mybatis底层实现这一块的问题,而且还不止一个小伙伴问到,于是我似乎认识到了问题的严重性,我花了两天时间整理了一下自己的认识和网上查阅的资料写了这篇 ...

  6. Mybatis Interceptor 拦截器原理 源码分析

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最 ...

  7. MyBatis:二级缓存原理分析

    MyBatis从入门到放弃七:二级缓存原理分析 前言 说起mybatis的一级缓存和二级缓存我特意问了几个身边的朋友他们平时会不会用,结果没有一个人平时业务场景中用. 好吧,那我暂且用来学习源码吧.一 ...

  8. Mybatis的SqlSession运行原理

    前言 SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开 ...

  9. mybatis的Mapper代理原理

    前言:在mybatis的使用中,我们会习惯采用XXMapper.java+XXMapper.xml(两个文件的名字必须保持一致)的模式来开发dao层,那么问题来了,在XXMapper的文件里只有接口, ...

随机推荐

  1. 【Azure 应用服务】App Service与APIM同时集成到同一个虚拟网络后,如何通过内网访问内部VNET的APIM呢?

    问题描述 App Service访问的APIM已配置内部虚拟网络(Internal VNet)并拥有内网IP地址.App Service与APIM都在相同的虚拟网络(VNET)中.App Servic ...

  2. Redis缓存中的常见问题

    缓存穿透:是指查询一个Redis和数据库中都不存在的数据. 问题:查询一个Redis和数据库中都不存在的数据,大量请求去访问数据库,导致数据库宕机. 解决办法: 1.根据id查询,如果id是自增的,将 ...

  3. JavaScript疑难点

    什么是闭包 我个人理解闭包就是函数中嵌套函数,但是嵌套的那个函数必须是返回值,才构成闭包: //标准的闭包 function fn(){ var i=1; return function fnn(){ ...

  4. Linear Algebra From Data

    Linear Algebra Learning From Data 1.1 Multiplication Ax Using Columns of A 有关于矩阵乘法的理解深入 矩阵乘法理解为左侧有是一 ...

  5. kali msf6 更新及bug处理

    问题描述 Metasploit 漏洞库更新,利用msfupdate命令更新,出现已停止该命令更新,出现如下提示: 利用一句话安装更新,命令如下,安装过程中有部分警告出现 curl https://ra ...

  6. pytest+jenkins+allure 生成测试报告发送邮件

    前言第一部分:Pycharm for Gitee1. pycharm安装gitee插件2. gitee关联本地Git快速设置- 如果你知道该怎么操作,直接使用下面的地址简易的命令行入门教程:3. Gi ...

  7. POJ_2387 Til the Cows Come Hom 【最短路】

    一.题目 POJ2387 二.分析 Bellman-Ford算法 该算法是求单源最短路的,核心思想就是不断去更新到起点的最短距离,更新的前提是没有负边.如果有负边需要手动控制循环次数. Dijkstr ...

  8. 20个最有用的Python数据科学库

    核心库与统计 1. NumPy(提交:17911,贡献者:641) 一般我们会将科学领域的库作为清单打头,NumPy 是该领域的主要软件库之一.它旨在处理大型的多维数组和矩阵,并提供了很多高级的数学函 ...

  9. Python中面向对象的概念

    1.语言的分类 1)面向机器 抽象成机器指令,机器容易理解.代表:汇编语言. 2)面向过程 做一件事,排除步骤,第一步做什么,第二步做什么,如果出现A问题,做什么处理,出现b问题,做什么处理.问题规模 ...

  10. python打印9宫格25宫格81宫格.....

    """ 2 问题描述: 3 给定一个奇数(num),生成一个横竖斜加起来的和相等 4 问题解析: 5 这其实就是一个九宫格的问题 6 九宫格问题的解答技巧: 7 1要放在 ...