背景

项目需要,我们需要自己做一套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. 交叉验证分析每一折(fold of Kfold)验证数据的评估指标并绘制综合ROC曲线
  2. java-mybaits-00101-基础安装配制
  3. Vim实战指南(一):基础编辑命令
  4. python完全新手教程-小白的Python新手教程​
  5. [转]windows中断与共享的连接(samba)
  6. 【前端】vue.js实现按钮的动态绑定
  7. 文巾解题 LCP 11. 期望个数统计
  8. mac下面安装mysql
  9. api数据库管理_API管理平台如何增强您的数据科学项目
  10. 【Lucy-Richardson去卷积】迭代加速算法
  11. 【多线程】并发执行指定数量的线程
  12. 2017.10.6 Java命名规范及使用情况
  13. 我眼中的Oracle Database Software 和 Oracle Database
  14. python全套教程大全-千锋出品全套python视频教程,400大全集,你了解吗?
  15. 韩顺平python教程视频_尚硅谷_韩顺平_Linux_2018Linux基础入门教程全集
  16. 苹果 2019 卖什么?新 iPhone 值不值得买?
  17. android系统可以识别NTFS格式吗,安卓手机支持ntfs格式的储存卡吗
  18. 天原笔记(2)——气团与锋
  19. 生产线是什么意思_食品仓储托管小型仓库出租信息什么意思
  20. 北上深程序猿纷纷入杭!杭州互联网工程师人才净流入率全国第一

热门文章

  1. 工业大数据漫谈4:工业大数据的作用
  2. Word加粗的字体如何恢复正常粗细
  3. ParaView整体介绍
  4. journald.conf 中文手册
  5. 【C语言】把一个结构体指针转换为另一个结构体指针
  6. IDEA相对路径系统找不到指定的文件问题
  7. FileNotFoundError: [WinError 2] 系统找不到指定的文件
  8. 计算机网络实践之元气骑士公网异地联机(三) 完善转发机的转发规则
  9. ip address转换
  10. Prometheus 之 Alertmanager告警抑制与静默