mybatis的本质和原理
背景
项目需要,我们需要自己做一套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的本质和原理的更多相关文章
- mybatis笔记3 一些原理的理解
1,mybatis流程跟踪,原理理解 基本思路: 从SqlSessionFactory的初始化出发,观察资源的准备和环境的准备,以及实现持久层的一些过程: 进入SqlSessionFactoryBea ...
- atitit.查看预编译sql问号 本质and原理and查看原生sql语句
atitit.查看预编译sql问号 本质and原理and查看原生sql语句 1. 预编译原理. 1 2. preparedStatement 有三大优点: 1 3. How to look gene ...
- Mybatis拦截器实现原理深度分析
1.拦截器简介 拦截器可以说使我们平时开发经常用到的技术了,Spring AOP.Mybatis自定义插件原理都是基于拦截器实现的,而拦截器又是以动态代理为基础实现的,每个框架对拦截器的实现不完全相同 ...
- mybatis入门-mapper代理原理
原始dao层开发 在我们用mybatis开发了第一个小程序后,相信大家对于dao层的开发其实已经有了一个大概的思路了.其他的配置不用变,将原来的test方法,该为dao的方法,将原来的返回值,直接在d ...
- 深度分析:mybatis的底层实现原理,看完你学会了吗?
前言 最近在和粉丝聊天的时候被粉丝问到jdbc和mybatis底层实现这一块的问题,而且还不止一个小伙伴问到,于是我似乎认识到了问题的严重性,我花了两天时间整理了一下自己的认识和网上查阅的资料写了这篇 ...
- Mybatis Interceptor 拦截器原理 源码分析
Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最 ...
- MyBatis:二级缓存原理分析
MyBatis从入门到放弃七:二级缓存原理分析 前言 说起mybatis的一级缓存和二级缓存我特意问了几个身边的朋友他们平时会不会用,结果没有一个人平时业务场景中用. 好吧,那我暂且用来学习源码吧.一 ...
- Mybatis的SqlSession运行原理
前言 SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开 ...
- mybatis的Mapper代理原理
前言:在mybatis的使用中,我们会习惯采用XXMapper.java+XXMapper.xml(两个文件的名字必须保持一致)的模式来开发dao层,那么问题来了,在XXMapper的文件里只有接口, ...
随机推荐
- getter和setter以及defineProperty的用法
getter 和 setter 和 defineProperty getter:将对象属性绑定到查询该属性时将被调用的函数 说人话就是,当你调用一个getter属性时会调用定义好的get函数,这个函数 ...
- Portainer中文汉化
一.概述 Portainer是Docker的图形化管理工具,提供状态显示面板.应用模板快速部署.容器镜像网络数据卷的基本操作(包括上传下载镜像,创建容器等操作).事件日志显示.容器控制台操作.Swar ...
- 解决tui-editor布局错误失效问题(Vue-Element-Admin)
更新:突然想起来会不会是兼容或者版本问题? 使用tui-editor遇到markdown预览样式错误,也不清楚是不是版本的问题,改了下源码之后效果正常了 打开控制台看到几个类名不对劲,te-edito ...
- HDOJ-1213(简单并查集)
How many tables HDOJ-1213 #include<iostream> #include<cstring> #include<cstdio> #i ...
- mysql创建读写账号及服务相关优化配置
grant select on xoms_prod.* to 'kzcf'@'%' identified by '123456'; 赋权多权限就 grant select,update,delet ...
- FreeBSD ports 基本用法
首先获取portsnap#portsnap fetch extract---------------------------------------使用whereis 查询软件地址如#whereis ...
- Java基础:常用基础dos命令
打开cmd的方式1.开始+系统+命令提示符2.win键+R 输入cmd 打开控制台3.在任意的文件夹下,按住shift键+鼠标右键点击,在此处打开命令提示行4.在资源管理器的地址栏前面加上cmd路径 ...
- NoSQL安装与操作
redis操作: redis的启动与关闭:注意:(需要关闭防火墙) redis的启动:redis-server redis.conf redis的登录:redis-cli -a pass redis的 ...
- Android R 新特性分析及适配指南
Android R(Android 11 API 30)于2020年9月9日正式发布,随国内各终端厂商在售Android设备的版本更新升级,应用软件对Android R 版本的兼容适配已迫在眉睫. 对 ...
- 前端-CS-04
一:DOM(文档对象模型) document 简写DOM 1.DOM中定义变量用 var 如下截图中:定义demo变量 2.取一个input输入框中的值的方法: 1)先如1中,在dom中顶一个一个变 ...