MyBatis初解

MyBatis是一种半自动映射的框架。是目前较为流行的Java ORM框架。(ORM模型是指数据库的表与Java的POJO的映射关系模型,解决之间的相互映射。)本文主要是我在学习了《深入浅出MyBatis技术原理与实战》后的自我总结。

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 全局映射器启用缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 全局延时加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 不对带有延时加载属性的对象完全加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 对单一SQL允许返回多结果集 -->
<setting name="multipleResultSetsEnabled" value="true"/>
<!-- 允许使用列标签代替列名 -->
<setting name="useColumnLabel" value="true"/>
<!-- 允许使用自定义的主键值(比如由程序生成的UUID 32位编码作为键值),数据表的PK生成策略将被覆盖 -->
<setting name="useGeneratedKeys" value="true"/>
<!-- 自动映射任意复杂的结果集 -->
<setting name="autoMappingBehavior" value="FULL"/>
<!-- 简单执行器 -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 数据库超过25000秒仍未响应则超时 -->
<setting name="defaultStatementTimeout" value="25000"/>
<!-- 配置使用log4j记录日志-->
<setting name="logImpl" value="log4j"/>
<!-- 自动转换驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>

这是简单Mybatis的设置。依然还有许多属性我们没有提到。在我们的设置中,autoMappingBehavior是有三种设置:NONE(取消自动映射)、PARTIAL(只会映射没有定义嵌套结果集映射的结果集,在缺省配置的情况下默认)、FULL;而defaultExecutorType表示执行器executor类型,分为三种:SIMPLE(普通执行器,默认情况下是SIMPLE)、REUSE(执行器会重用预处理语句prepared statements)、BATCH(执行器会重用语句并执行批量更新)。

在configuration中还会涉及其他属性,常用的有typeAliases(类型命名)、typeHandler(类型处理器)、plugins(插件)等。而对于typeHandler的配置里,又有javaType与jdbcType,typeHandler就是解决其转换的问题。

MyBatis-Spring

一般情况下,我们大多数情况下是在Spring中使用MyBatis,即需要配置MyBatis-Spring。分为五步进行配置:

  • 配置数据源
  • 配置SqlSessionFactory
  • 配置SqlSessionTemple(使用Mapper接口编程方式,这儿的配置就隐藏了)
  • 配置Mapper
  • 事务处理

先配置数据源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="#{jdbc['jdbc.url']}"/>
<property name="username" value="#{jdbc['jdbc.username']}"/>
<property name="password" value="#{jdbc['jdbc.password']}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="minIdle" value="#{jdbc['ds.minIdle']}"/>
<property name="maxActive" value="#{jdbc['ds.maxActive']}"/>
<property name="initialSize" value="#{jdbc['ds.initialSize']}"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="#{jdbc['ds.maxWait']}"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="#{jdbc['ds.timeBetweenEvictionRunsMillis']}"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="#{jdbc['ds.minEvictableIdleTimeMillis']}"/>
<property name="validationQuery" value="SELECT 1"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="false"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat"/>
</bean>

这儿我们使用的Druid数据源。接下来配置SqlSessionFactory。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 主配置文件 -->
<property name="configLocation" value="classpath:/mybatis-config.xml"/>
<!-- 自动扫描sqlmap目录下的所有SQL映射的xml文件 -->
<property name="mapperLocations" value="classpath:mappers/*.xml"/>
<!-- 自动注册javabean别名 默认会使用javabean的首字母小写的非限定类名来作为它的别名-->
<property name="typeAliasesPackage" value="fei.self.model"/>
</bean>

<!-- spring与mybatis整合配置,扫描所有dao -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 扫描基本包路径下的所有映射器接口类 采用分号或者逗号分隔 可设置多个包路径 -->
<property name="basePackage" value="com.ximalaya.ops.fei.self.dao"/>
<!--多个数据源 可设置具体的sqlSessionFactoryBean 单个数据源不必配置-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

这儿的mybatis-config.xml就是我们之前的configuration以及setting的那个文件。并且在这儿,我们配置了自动扫描信息,包括扫描所有的DAO以及Mapper文件。接下来只剩下事务处理的配置了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 对dataSource 数据源进行事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>

<!-- 事务管理 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 对insert,update,delete 开头的方法进行事务管理,只要有异常就回滚 -->
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="reset*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="getExecution*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<!-- select,count开头的方法,开启只读,提高数据库访问性能 -->
<tx:method name="select*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<!-- 对其他方法 使用默认的事务管理 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- 启用对事务注解的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

这是最基础的MyBatis-Spring的配置。这部分其实挺无趣的,个人觉得MyBatis最有趣的就是接下来的MyBatis的技术原理以及插件等。关于Mapper的一些在这儿不作罗列了。

动态SQL

所谓动态SQL,指的是一些特殊的MyBatis标签的使用,从而对于SQL的拼装具有动态性的效果。主要是if、choose、trim、foreach以及bind元素这些。这部分其实挺有趣的,可以增加我们对于MyBatis的掌握。这部分的知识在这儿不作罗列,看一些例子都能明白。

MyBatis原理

终于到了重点且有趣的地方,这部分知识可以帮助我们理解MyBatis,然后能写一些好用的插件。

在学习之前需要掌握动态代理,分为JDK动态代理与CGLIB动态代理。

首先,需要构建SqlSessionFactory。第一步,先通过XMLConfigBuilder解析配置文件,存入Configuration类中(这个类里基本保存了所有的配置)。第二步,使用Configuration去构建SqlSessionFactory。对于SqlSessionFactory,这是一个接口,在一般MyBatis中用DefaultSqlSessionFactory的实现类,对于接口的方法都做了实现。

第二个需要掌握的是Mapper映射器。我们提到过,在Configuration中,有所有的配置,当然映射器也在里面。需要了解的是,Mapper映射是通过动态代理的方式实现的。一般映射器里面包含有三部分:MappedStatement:用户保存映射节点;SqlSource:这是MappedStatement的一个属性,一个接口,主要是根据参数和其他规则组装SQL,当然它提供BoundSql;BoundSql:建立SQL和参数的地方。我们一般修改SQL或者参数都是在BoundSql中修改的。对于BoundSql中如何实现多种参数的注入方式,我这儿就不讲解了。

既然有了SqlSessionFactory,那么我们很容易就得到SqlSession了。从Mapper映射器中,我们通过代理对象会进入到MapperMethod的execute方法。然后就能进入SqlSession的方法里了。我们需要了解的是SqlSession里的增删改查方法是如何实现的。

首先SqlSession下有四大对象。1、Executor执行器:用来调度StatementHandler、ParameterHandler、ResultHandler;2、StatementHandler:这个是在SqlSession里最重要的部分,它可以使数据库的Statement,即PreparedStatement执行操作(PreparedStatement接口是继承了Statement接口);3、ParamentHandler:用于SQL的参数处理;4、ResultHandler:用于最后返回数据集的封装。我学到这儿很疑惑这个Satement究竟是什么?Statement 对象用于执行不带参数的简单 SQL 语句;PreparedStatement 对象用于执行带或不带 IN 参数的预编译 SQL 语句;CallableStatement 对象用于执行对数据库已存在的存储过程的调用。我们一般在插件中使用的是PrepareStatement,这三者对应了三种数据库会话器,SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler。对于着Executor也分为三种SIMPLE、REUSE、BATCH。关于参数处理器以及结果处理器就不提及了。

SqlSesion内部运行图

插件

插件部分,我无法总结清晰,所以给出我的分页插件中重点intercept函数实现的基本流程图。

intercept函数

本文主要是个人的一些总结,没有完全梳理MyBatis的流程等,也没有完全涉及MyBatis的所有知识。