背景

项目需要,我们需要自己做一套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";@Overridepublic 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 拦截器执行原理分析

    目录 1.拦截器执行流程 2.拦截器实现原理 3.拦截器用法 1.mybatis拦截器实现原理 2.拦截器实现原理 在Mybaits中 拦截器需实现Interceptor接口,加上如下注解 @Inte ...

  2. mybatis传递多个参数_深入浅出MyBatis:MyBatis解析和运行原理

    原文:https://juejin.im/post/5abcbd946fb9a028d1412efc 本篇文章是「深入浅出MyBatis:技术原理与实践」书籍的总结笔记. 上一篇介绍了反射和动态代理基 ...

  3. MyBatis 动态 SQL 底层原理分析

    MyBatis 动态 SQL 底层原理分析 我们在使用mybatis的时候,会在xml中编写sql语句. 比如这段动态sql代码: <update id="update" p ...

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

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

  5. mybatis 二级缓存失效_给我五分钟,带你彻底掌握MyBatis的缓存工作原理

    前言 在计算机的世界中,缓存无处不在,操作系统有操作系统的缓存,数据库也会有数据库的缓存,各种中间件如Redis也是用来充当缓存的作用,编程语言中又可以利用内存来作为缓存.自然的,作为一款优秀的ORM ...

  6. 给我五分钟,带你彻底掌握 MyBatis 缓存的工作原理

    前言 在计算机的世界中,缓存无处不在,操作系统有操作系统的缓存,数据库也会有数据库的缓存,各种中间件如Redis也是用来充当缓存的作用,编程语言中又可以利用内存来作为缓存.自然的,作为一款优秀的ORM ...

  7. MyBatis 的基本工作原理

    博客地址:www.lxiaocode.com MyBatis 是一款优秀的持久层框架.于原生的 JDBC 相比,MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的繁琐操作. ...

  8. 【Mybatis+spring整合源码探秘】--- mybatis整合spring事务原理

    文章目录 1 mybatis整合spring事务原理 1 mybatis整合spring事务原理 本篇文章不再对源码进行具体的解读了,仅仅做了下面一张图: 该图整理了spring+mybatis整合后 ...

  9. 矩阵求导(本质、原理与推导)详解

    矩阵求导是机器学习与深度学习的基础,它是高等数学.线性代数知识的综合,并推动了概率论与数理统计向多元统计的发展.在一般的线性代数的课程中,很少会提到矩阵导数的概念:而且在网上寻找矩阵求导的知识点,也是 ...

最新文章

  1. [ 1001] 动态开辟二维数组的说明
  2. RHEL环境下调试Shell脚本时遇到字符串转换整数的问题
  3. linux mysql安装教程 方大帝_discuz论坛出现Can not connect to MySQL server错误的解决方法...
  4. 联想计算机如何设置用户名和密码,联想电脑怎样设密码?联想电脑设置密码方法步骤【图文】...
  5. 想成领袖?先瞄准老板身边的位置
  6. 校验身份证_Excel每日一技巧:从身份证号可以提取哪些信息呢?
  7. mybatis的动态sql学习注意点!!!
  8. JavaScript的作用域(1)
  9. Android Menu
  10. Iocomp 工业仪表盘控件包详细介绍说明
  11. Windows开机自动运行.py文件
  12. 在ubuntu中使用visual studio code对C/C++文件调试
  13. 在使用缓冲流时,遇到Stream closed异常提醒
  14. s5p4418挂载nfs文件系统
  15. On SDK version 23 and up, your app data will be automatically backed up and restored on app install.
  16. 医咖会stata 笔记(自己能看懂版
  17. dwz php 联动,PHP利用DWZ.CN服务生成短网址
  18. 【淘宝】从开店到运营 - 开店前期知识(天猫、淘宝店铺介绍)
  19. 计算机视觉中的边缘检测
  20. html盒子模型图片居中,html学习笔记(img+body+盒子模型+块级元素居中+margi塌陷+float+文字围绕、打点)...

热门文章

  1. jQuery(一)jQuery的认识和使用
  2. [笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV视频lO接口
  3. 音频基础知识-PCM、AAC
  4. Linux驱动开发-编写PCF8591(ADC)芯片驱动
  5. 【Conda】常用命令
  6. 孩子立刻就不玩游戏了,因为他找到更好玩的东西!
  7. 简单的分布式矩阵乘法
  8. java基础 —— 集合、异常、反射、io流、多线程
  9. 用python编写一个篮球计分系统_毕业设计(3)基于MicroPython的篮球计时计分器模型的设计与实现...
  10. 网络工具netstat与ss