本文是作者原创,版权归作者所有.若要转载,请注明出处.本文以简单的insert语句为例,只贴我觉得比较重要的源码,其他不重要非关键的就不贴了

1.mybatis的底层是jdbc操作,我们先来回顾一下insert语句的执行流程,如下

//连接数据库
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost/kaikeba?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8";
String user="root";
String password="root";
//建立连接
Connection conn = DriverManager.getConnection(url, user, password);
//sql语句
String sql="INSERT INTO dept(DNAME)VALUES (?)";
//获得预处理对象
PreparedStatement preparedStatement = conn.prepareStatement(sql);
//将SQL语句占位符位置设置实际参数
preparedStatement.setString(1, "T"); //为问号赋值
//增删改都使用executeUpdate()提交,返回值是int,表示有多少条数据受到了影响
int i = preparedStatement.executeUpdate();
System.out.println(i);
//释放资源
preparedStatement.close();
conn.close();

执行完后,我们看下数据库结果

OK,语句生效了.

2.贴一下我用mybatis框架写的demo,贴一下主要代码,依次是写sql的xml,以及该xml所对应的接口,还有个是测试的代码

还有mybatis的配置信息

<configuration>

    <!-- 指定properties配置文件, 我这里面配置的是数据库相关 -->
<properties resource="db.properties"></properties> <settings>
<!-- 该配置影响的所有映射器中配置的缓存的全局开关。默认值true -->
<setting name="cacheEnabled" value="true"/>
<!--配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句; BATCH 执行器将重用语句并执行批量更新。默认SIMPLE -->
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings> <!--别名-->
<typeAliases>
<typeAlias type="com.lusaisai.po.DeptInfo" alias="deptInfo"></typeAlias>
</typeAliases> <!-- 和spring整合后mybatis的 environments配置将废除 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments> <!-- 加载mapper.xml -->
<mappers>
<!-- 第一种方式:通过resource指定 -->
<mapper resource="com/lusaisai/mapper/DemoMapper.xml" ></mapper>
<!--第二种方式,直接指定包,自动扫描-->
<!--<package name="com.lusaisai.mapper.*"/>-->
</mappers> </configuration>

执行完后,我们看下数据库结果

OK,语句生效了.

3.现在是开始阅读源码环节,我在源码上加了自己的理解,这就是我上篇文章编译mybatis源码的好处,否则就只能干看了

从读取配置文件下面一行代码开始

//就是这个方法,注意后面两个参数均为空
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//XMLConfigBuilder看这个名字就是对mybatis的配置文件进行解析的类,现在他还是一个初始化对象,没有开始解析.用的是java的dom解析
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//这个就是读取方法,parser.parse()返回一个Configuration对象,该对象将存放读取配置文件的信息
//build(parser.parse())传入一个Configuration对象,并使用多态返回SqlSessionFactory接口的实体类DefaultSqlSessionFactory
//DefaultSqlSessionFactory只有一个属性,就是Configuration对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
//这里关闭了读取流inputStream,我们不用再手动关闭了
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

一层一层看下去:先内层,返回Configuration对象,这个对象是用来存储解析xml文件的信息的

//调用此方法对mybatis配置文件进行解析
public Configuration parse() {
//注意:parsed默认为false,配置文件读取非常消耗资源,因此这里只读取一次,如果读取过则将parsed的值改为true,再次读取的时候就抛一个异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//在这里将parsed的值改为true
parsed = true;
//从配置文件configuration标签开始解析,具体解析过程在下面
parseConfiguration(parser.evalNode("/configuration"));
//返回Configuration对象用于存储mybatis配置文件信息
return configuration;
}
//此方法就是解析configuration节点下的子节点
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first,这里先读取properties标签,也就是数据库配置
//我们在configuration下面能配置的节点为以下10个节点
//properties标签这里一般配置数据源
propertiesElement(root.evalNode("properties"));
//settings标签可以配置缓存,执行器等信息
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));//解析别名,点进去看下
pluginElement(root.evalNode("plugins"));//解析插件
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));//解析jdbc信息
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));//解析mapper
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
//解析settings标签,返回Properties对象
private Properties settingsAsProperties(XNode context) {
//如果没有settings标签,给一个默认值
if (context == null) {
return new Properties();
}
//如果有settings标签,解析
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}

 我们可以看到返回的Properties对象就是我们mybatis的配置信息里的值,当然还有很多默认属性这里就不一一列举了

下面我们看下怎么解析别名的

//解析别名的方法
private void typeAliasesElement(XNode parent) {
//解析别名标签
if (parent != null) {
for (XNode child : parent.getChildren()) {
//批量解析
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {//一个一个解析
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
//根据type表示的全限定名拿到它的class对象
Class<?> clazz = Resources.classForName(type);
//注册别名,分两种情况,一种别名alias为空,一种不为空
if (alias == null) {//别名alias为空
typeAliasRegistry.registerAlias(clazz);
} else {//别名alias不为空
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
//Alias为空的注册别名方法
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
//看下注解上面有没有
Alias aliasAnnotation = type.getAnnotation(Alias.class);
//如果有注解,解析注解
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
//否则,解析xml
registerAlias(alias, type);
} //这就是注册别名的本质方法, 其实就是向保存别名的hashMap新增值而已
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
//别名统一处理,都转化为小写,所以别名不区分大小写
String key = alias.toLowerCase(Locale.ENGLISH);
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
//将别名存进map
typeAliases.put(key, value);
}
//原来别名就通过一个HashMap来实现, key为别名, value就是别名对应的类型(class对象)
private final Map<String, Class<?>> typeAliases = new HashMap<>(); //别名在这这个构造方法做默认的处理,以下就是mybatis默认为我们注册的预制别名
public TypeAliasRegistry() {
registerAlias("string", String.class); registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class); registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class); registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class); registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class); registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class); registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class); registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class); registerAlias("ResultSet", ResultSet.class);
}

我们再看下如何解析mappers标签

//解析com/lusaisai/mapper/DemoMapper.xml 方法
private void configurationElement(XNode context) {
try {
//解析命名空间
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));//解析缓存标签
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));//解析resultMap标签
sqlElement(context.evalNodes("/mapper/sql"));//解析sql标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//这里四种标签一起解析,所以效果一样
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

我们再看下mybatis是怎么解析sql的,贴一下重要代码

 可以看到originalSql 就是我们自己写的代码,在这个方法里将#{和}里的参数替换为占位符并返回

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
//INSERT INTO dept(DNAME) VALUES (#{dName}) 将#{和}里的参数替换为占位符?
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  //这里点进去
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
这个就是解析sql的方法,主要方式是截取我们写的sql语句,将#{}的地方循环截取掉,将里面的参数改成?

public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//这里就是将#{}里的参数 替换为?的方法
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
 

点进去看下,找到了,就是这里将#{}里的参数替换为占位符?
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}

最后封装到Configuration对象的mappedStatements属性的sqlsource里面

到此为止,我们已经把配置文件中的所有配置封装到Configuration对象中.

我们看下解析完的Configuration对象都有哪些属性,首先是jdbc配置参数

然后是这个,混个眼熟,name的值是SIMPLE

然后是这个,刚刚我们看到sql相关

,

点开看一下

我们可以看到有sql的xml信息和接口信息.

好,回到这里,继续下一层

点进去

好了,这里我们知道其实返回了DefaultSqlSessionFactory对象.继续看下去

这里帮我们关闭了读取流inputStream,我们不用再手动关闭了,继续看下去

返回的DefaultSqlSessionFactory就是SqlSessionFactory接口的实现类,
这个类只有一个属性,就是Configuration对象,Configuration对象用来存放读取xml配置的信息
继续下一行,点进去

刚刚我们看到过这个属性,不知道干啥的,很眼熟,name的值是SIMPLE,继续往下看

看下tx这个对象,其他的不仔细看了

(猜测:有个属性autoCommit默认是false,不自动提交,这里就对应了上面,我们手动提交事务了,继续往下看DefaultSqlSession)

我们发现和上文一样,也是SqlSession接口的实现类

现在,可以走出去了

返回的DefaultSqlSession是SqlSession接口的实现类,它应该是与数据库打交道的对象,
对象中有上面传来的Configuration对象,还有executor处理器对象,executor处理器有一个dirty属性,默认为false,如下图

现在进入sqlSession的insert方法看下

继续点进去看下

可以看到,我们拿出了上面提到的sql相关的信息,我们看下ms有哪些属性

ms对象有两个重要属性:sqlSource是用来存sql语句的,id是DemoMapper接口的.insertDept方法名
继续往下看,下面那个方法应该就是执行的方法了,一层一层点进去

这里判断参数是什么类型,我们是string类型,直接原样返回,其他集合类型,数组类型有是专门的处理,继续点下去

我们点进那个执行方法

继续点进那个执行方法

我们看下handler对象,它是Statement对象的处理器,看看它有哪些属性,如下

我们可以看到,里面有一条完整的可执行语句,我猜下面就是jdbc操作了,点进去看下

真是失望,看源码就是封装的太多了,看着看着就晕了,继续点进去看

终于看到PreparedStatement预编译对象了,

我们可以看下返回rows那行代码,点进去发现如下

执行了ClientPreparedStatement对象的方法,这个对象好像没见过

别着急,我们在idea内点击右键,选择 Diagrams,查看类的继承关系图

好了,我们知道这个对象本质也是PreparedStatement对象,在那个方法里返回了受影响的行数,其他的目前不必深究

到这里我们就要结束了,最后看下这个commit方法吧,点进去

注意,这里有个dirty属性为true,我猜是脏数据,不会写入数据库,我们让他继续执行

变成false了,哈哈,现在不是脏数据了,肯定可以写入数据库了.最后看下数据库

,这些数据是我debug好几次写入的数据,不管它们,我们把commit方法注释掉,再执行一次

我们把注释放开,继续执行一次

所以insert语句要commit,手动执行提交事件,将数据提交到数据库中,才真正成功插入到数据库中,否则会回滚,数据库看不到数据
好了,这篇文章到这里就结束了.谢谢大家的观看,大家有空的话就帮我点个赞吧
文章中若有错误和疏漏之处,还请各位大佬不吝指出
若要下载我的源代码和注释,这里是我的GitHub地址:https://github.com/lusaisai1019/mybatis-master

mybatis源码专题(2)--------一起来看下使用mybatis框架的insert语句的源码执行流程吧的更多相关文章

  1. (3)一起来看下使用mybatis框架的select语句的源码执行流程吧

    本文是作者原创,版权归作者所有.若要转载,请注明出处.本文以简单的select语句为例,只贴我觉得比较重要的源码,其他不重要非关键的就不贴了 主流程和insert语句差不多,这里主要讲不同的流程,前面 ...

  2. mybatis源码专题(1)--------复习jdbc操作,编译mybatis源码,准备为你的简历加分吧

    本文是作者原创,版权归作者所有.若要转载,请注明出处.文章中若有错误和疏漏之处,还请各位大佬不吝指出,谢谢大家. 1.mybatis的底层是jdbc操作,我们来回顾一下,如下  运行以后的结果如下图: ...

  3. Mybatis常用操作 专题

    parameterType:即将传入的语句参数的完全限定类名和别名.这个属性是可选项的,因为MyBatis可以推断出传入语句的具体参数,因此不建议配置该属性flushCache:默认值为true,任何 ...

  4. [源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器

    [源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器 目录 [源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器 0x0 ...

  5. MyBatis学习总结(一)——ORM概要与MyBatis快速起步

    程序员应该将核心关注点放在业务上,而不应该将时间过多的浪费在CRUD中,多数的ORM框架都把增加.修改与删除做得非常不错了,然后数据库中查询无疑是使用频次最高.复杂度大.与性能密切相关的操作,我们希望 ...

  6. (4)一起来看下mybatis框架的缓存原理吧

    本文是作者原创,版权归作者所有.若要转载,请注明出处.本文只贴我觉得比较重要的源码,其他不重要非关键的就不贴了 我们知道.使用缓存可以更快的获取数据,避免频繁直接查询数据库,节省资源. MyBatis ...

  7. 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)

    最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前九篇中,介绍了mybatis的配置以及使用, 那么本篇将走进mybatis的源码,分析mybatis 的执行流程, ...

  8. 深入浅出Mybatis系列十-SQL执行流程分析(源码篇)

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前 ...

  9. Mybatis源码系列 执行流程(一)

    1.Mybatis的使用 public static void main(String[] args) throws IOException { //1.获取配置文件流 InputStream is ...

随机推荐

  1. 重学计算机组成原理(十)- "烫烫烫"乱码的由来

    程序 = 算法 + 数据结构 对应到计算机的组成原理(硬件层面) 算法 --- 各种计算机指令 数据结构 --- 二进制数据 计算机用0/1组成的二进制,来表示所有信息 程序指令用到的机器码,是使用二 ...

  2. webpack4 前端框架基础配置实例-解决css分离图片路径问题

    1.安装nodejs 2. 需要全局和项目安装webpack和webpack-dev-server npm install webpack webpack-dev-server -g npm inst ...

  3. Nacos(四):SpringCloud项目中接入Nacos作为配置中心

    前言 通过前两篇文章: Nacos(二):Nacos与OpenFeign的对接使用 Nacos(三):SpringCloud项目中接入Nacos作为注册中心 相信大家已经对Nacos作为注册中心的基本 ...

  4. Django+zTree构建组织架构树

    树,因其清晰明了的展现形式而被广泛的使用 日常的开发过程中我们需要经常与"树"打交道,例如公司的组织架构树.服务器的项目归属树,管理后台侧边树等等,本篇文章介绍关于树的两个内容 多 ...

  5. .NET Core 小程序开发零基础系列(2)——小程序服务通知(模板消息)

    基于上一篇文件“.NET Core 小程序开发零基础系列(1)——开发者启用并校验牵手成功”的反映,个人觉得效果很不错,大家对公众号开发还是有很大需求的,同时也收到了很多同学的问题,后面我也会通过实战 ...

  6. Docker搭建Zookeeper&Kafka集群

    最近在学习Kafka,准备测试集群状态的时候感觉无论是开三台虚拟机或者在一台虚拟机开辟三个不同的端口号都太麻烦了(嗯..主要是懒). 环境准备 一台可以上网且有CentOS7虚拟机的电脑 为什么使用虚 ...

  7. Linux shell 内部命令与外部命令有什么区别以及怎么辨别

    内部命令实际上是shell程序的一部分,其中包含的是一些比较简单的linux系统命令,这些命令由shell程序识别并在shell程序内部完成运行,通常在linux系统加载运行时shell就被加载并驻留 ...

  8. 【StyleCop】StyleCop规则汇总

    所有规则的翻译(基于版本4.7.44.0): 文档规则 1.SA1600:ElementsMustBeDocumented元素必须添加注释 2.SA1601: PartialElementsMustB ...

  9. 2019DX#10

    Solved Pro.ID Title Ratio(Accepted / Submitted)   1001 Minimum Spanning Trees 22.22%(2/9)   1002 Lin ...

  10. B-Quadratic equation_2019牛客暑期多校训练营(第九场)

    题意 解下列方程 \((x+y) \equiv b \ mod \ p\) \((x\ *\ y) \equiv c \ mod \ p\) 题解 \(y = b-x\) 带入二式 \(x * (b- ...