Mybatis实现原理探究-实现部分Mybatis功能(上)
一、前言:
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录
通过Mybatis框架,可以将一个URL指向某段SQL代码,在Java代码里只需指定这个URL即可,然后如果代码进行了修改涉及到SQL的变更则可以在Mapper文件里统一查找并动态的修改。
为了能够更好的理解Mybatis的实现原理,个人认为最好的办法就是对其各个类型的功能都实现一遍,这个实现可以不涉及各种优化、异常处理和设计模式的应用等;通过实现这些功能一来是能够最直接的理解它的原理,二来也是为自己将来能够组合成一系列产品打下基础。
所有的框架的实现都离不开反射,不清楚怎么用反射的一定要多写一些demo去积累。
二、实现的Mybatis的功能:
在这个demo里只是先实现一个session.selectOne(statement, params)的功能(对于其他如delete、insert原理差不多),至于对数据库连接池的集成(如Druid)和其它的一些高级功能(比如各种缓存、优化、解耦等等)等有机会也是要实现一番方能有更深的理解。
三、准备工作:
1.MySQL里建立student表,里面有字段uid:bigint、name:varchar、no:varchar、gender:varchar;并新增几条不同记录。
2.创建StudentMapper.xml文件,其内容为:
<?xml version="1.0" encoding="utf-8"?>
<mapper namespace="me.silentdoer.simulatemybatis.mapping.StudentMapper">
<select id="getSingleStudent" parameterType="java.lang.Long"
resultType="me.silentdoer.simulatemybatis.pojo.Student">
select no, name from student where uid=#{?}
</select>
<!-- 其中parameterType可选 -->
</mapper>
3.在对应位置新建Student类(返回类型也可以用Map来代替具体的pojo类/一条记录,其中key是列名,值是具体值):
package me.silentdoer.simulatemybatis.pojo; /**
* @Author Silentdoer
* @Since 1.0
* @Version 1.0
* @Date 2018-2-19 19:16
*/
public class Student {
private Long uid;
private String no;
private String name;
private String gender;
public Student(){
this.uid = -1L;
}
public Long getUid() { return uid; } public void setUid(Long uid) { this.uid = uid; } public String getNo() { return no; } public void setNo(String no) { this.no = no; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString(){ return String.format("Student [uid=%s, no=%s, name=%s, gender=%s]", this.uid, this.no, this.name, this.gender); } }
四、代码实现:
1.新建类MySession:
package me.silentdoer.simulatemybatis.core; import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.sql.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream; /**
* @Author Silentdoer
* @Since 1.0
* @Version 1.0
* @Date 2018-2-19 18:47
*/
public class MySession {
private Set<String> opsSet; public void init(){
opsSet = new HashSet<>(4);
opsSet.addAll(Arrays.asList("select", "insert", "update", "delete"));
} // me.silentdoer.simulatemybatis.mapping.StudentMapper.getSingleStudent, 2L
public <T> T selectOne(String statement, Object ... params) throws DocumentException, NoSuchMethodException, ClassNotFoundException, SQLException, IllegalAccessException, InstantiationException, InvocationTargetException {
Object result = null;
//region 加载Mapper文件,提取SQL语句
SAXReader saxReader = new SAXReader();
// 这个Mapper文件应该由一个专门的解析器去解析并缓存起来,这里只是为了了解原理故不做这些优化。
URL mapperConfig = Thread.currentThread().getContextClassLoader().getResource("me/silentdoer/simulatemybatis/mapping/StudentMapper.xml");
//System.out.println(mapperConfig == null);
Document doc = saxReader.read(mapperConfig);
Element mapper = doc.getRootElement();
/**获取Mapper的名称空间,如me.silentdoer.simulatemybatis.mapping.StudentMapper*/
Attribute mapperNs = mapper.attribute("namespace");
if(!statement.contains(mapperNs.getValue())){
throw new NoSuchMethodException("没有此Mapper文件");
}
String part = statement.substring(statement.lastIndexOf(".") + 1); // 具体的方法如getSingleStudent
List<Element> selects = mapper.elements("select");
//Element ele = mapper.elementByID(part); // 本质上也是通过attribute实现的,但是这个ID似乎只能大写的(看了下源码应该是的)
Element partEle = null;
for(Element ele : selects){
String id = ele.attributeValue("id");
if(id.equals(part)){
partEle = ele;
break;
}
}
if(partEle == null){
throw new IllegalArgumentException("Mapper里没有提供对应的方法");
}
// java.lang.Long
String paramType = partEle.attributeValue("parameterType");
// me.silentdoer.simulatemybatis.pojo.Student
String resultType = partEle.attributeValue("resultType"); // resultMap只是再做了一个映射,自己使用Mybatis最好为每个pojo都做resultMap的映射
// select name, gender from student where uid=#{?}
String sql = partEle.getTextTrim();
// 判断sql里是否包含参数
if(sql.contains("#")){ // $这种暂且不考虑
//select * from student where uid=?
sql = sql.replaceAll("#\\{.+?}", "?");
}
/** 开始真正去操作数据库了 */
//ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");
Class.forName("com.mysql.jdbc.Driver"); // 注册驱动,为了能正确解析MySQL服务传来的数据和正确发送数据给MySQL服务
// Druid的initialSize可以通过这个方法初始化一个连接并将其存入连接池,隔一定空闲时间要发一些心跳数据给MySQL服务器防止被关闭
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_test", "root", "12345678");
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setObject(1, Class.forName(paramType).cast(params[0]));
ResultSet resultSet = preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int colNum = metaData.getColumnCount();
result = Class.forName(resultType).newInstance();
// 这里先不做结果集是否只有一条记录的判断
resultSet.next();
for(int i=1;i<=colNum;i++){
// 假设都是小写字母
final String name = metaData.getColumnName(i); // such as uid
Object nwVal = resultSet.getObject(i); // 要考虑到这个值可能是null,且比如boolean在数据库里设置为tinyint这里需要自己强制转换一下
Method[] methods = result.getClass().getMethods();
Stream<Method> methodsStream = Arrays.asList(methods).stream();
Method setter = methodsStream.filter((e) -> e.getName().equals("set".concat(name.substring(0, 1).toUpperCase().concat(name.substring(1))))).findFirst().get();
//System.out.println(setter.getName().concat("#").concat(nwVal + ""));
setter.invoke(result, nwVal);
}
//endregion
return (T)result;
}
}
2.在main方法里调用:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, SQLException, DocumentException, InvocationTargetException, ClassNotFoundException {
MySession session = new MySession();
Student result = session.selectOne("me.silentdoer.simulatemybatis.mapping.StudentMapper.getSingleStudent", 2L);
System.out.println(result);
}
最终输出结果为:
Student [uid=-1, no=Stu10002, name=Silentdoer2, gender=male],可见我们只select no和name则只是在Student的默认对象的基础上再修改了这两个属性的值。
对于session的其它方法,如insert、delete等内部原理都差不多,都是通过PreparedStatement来最终实现操作数据库的;如果参数是${?}的话则是通过拼串的形式实现该参数的传入,这种方式容易产生安全漏洞(有SQL注入的风险)。
Mybatis实现原理探究-实现部分Mybatis功能(上)的更多相关文章
- 如何完美回答面试官问的Mybatis初始化原理!!!
前言 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程. MyBatis的初始化做了什么 MyBatis基于XML配置文件 ...
- 【转】MaBatis学习---源码分析MyBatis缓存原理
[原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...
- 深入理解MyBatis的原理:整个体系
前言:工作中虽然用到了 MyBatis,可完全不知道为什么,再不学习就晚了,这里将记录我的学习笔记,整个 MyBatis 的体系. 一.简介 1.传统的JDBC JDBC 是一种典型的桥接模式. 使用 ...
- Mybatis框架(8)---Mybatis插件原理
Mybatis插件原理 在实际开发过程中,我们经常使用的Mybaits插件就是分页插件了,通过分页插件我们可以在不用写count语句和limit的情况下就可以获取分页后的数据,给我们开发带来很大 的便 ...
- SpringBoot集成MyBatis底层原理及简易实现
MyBatis是可以说是目前最主流的Spring持久层框架了,本文主要探讨SpringBoot集成MyBatis的底层原理.完整代码可移步Github. 如何使用MyBatis 一般情况下,我们在Sp ...
- 从源码角度分析 MyBatis 工作原理
一.MyBatis 完整示例 这里,我将以一个入门级的示例来演示 MyBatis 是如何工作的. 注:本文后面章节中的原理.源码部分也将基于这个示例来进行讲解.完整示例源码地址 1.1. 数据库准备 ...
- Mybatis 缓存原理
Mybatis 缓存原理 本文来自拉钩 java 高薪训练营,如果文章写的不好,看不懂可以找我要课程视频,不收费. 只愿在编程道路上,寻求志同道合的码友.v:15774135883 1 Mybatis ...
- MyBatis工作原理
Mybatis工作原理: 我们的应用程序通过mybatis提供的api,增删改查方法来访问数据库,api底层调用了jdbc ,只不过mybatis对jdbc的封装是不完全封装,里面的sql语句需要我们 ...
- Mybatis插件原理分析(二)
在上一篇中Mybatis插件原理分析(一)中我们主要介绍了一下Mybatis插件相关的几个类的源码,并对源码进行了一些解释,接下来我们通过一个简单的插件实现来对Mybatis插件的运行流程进行分析. ...
随机推荐
- 将Ctrl+Alt+Delete键进行屏蔽,防止误操作重启服务器
[root@bgw-t ~]# vi /etc/init/control-alt-delete.conf #exec /sbin/shutdown -r now "Control-Alt- ...
- 【完结汇总】iKcamp出品基于Koa2搭建Node.js实战共十一堂课(含视频)
- jstl遍历list的jsp
jstl,核心标签库,使用,<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%& ...
- javascript获取事件源对象和产生事件的对象
事件源对象是指event对象,其封装了与事件相关的详细信息,比如按键状态. 获取事件源对象的方法 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. ...
- oracle 网络环境配置
PLSQL Developer连接Oracle11g 64位数据库配置详解 最近换了台64bit的电脑,所以oracle数据库也跟着换成了64bit的,不过 问题也随之产生,由于plsql devel ...
- windows本地blast
详细可参考https://www.jianshu.com/p/2f125cdf8262:https://blog.csdn.net/qq_34296043/article/details/544277 ...
- 利用简单的参数传递来实现单条查询的easyui-datagrid
前一阵子老师给出了一个题目, 说让设计个表格, 学生系统的, 可以查询学生的信息和成绩, 科目自己定, 数据库建表也自己定. 数据库的建表可是建的相当的简陋, 反正老师不是很满意, 后来数据表格做出来 ...
- (动态规划)有 n 个学生站成一排,每个学生有一个能力值,从这 n 个学生中按照顺序选取kk 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 kk 个学生的能力值的乘积最大,返回最大的乘积
第2关:最强战队 挑战任务 绿盟和各大名企合作,举办编程能力大赛,需要选拔一支参赛队伍.队伍成员全部来自“绿盟杯”中表现优秀的同学,每个同学都根据在比赛中的表现被赋予了一个能力值.现在被召集的N个同学 ...
- 64位tomcat不能配32位的JDK使用
警告: The APR based Apache Tomcat Native library failed to load. The error reported was [D:\apache-tom ...
- Java数据类型与MySql数据类型对照表
这篇文章主要介绍了Java数据类型与MySql数据类型对照表,以表格形式分析了java与mysql对应数据类型,并简单讲述了数据类型的选择与使用方法,需要的朋友可以参考下 本文讲述了Java数据类型与 ...