目录java
mybatis的基本概念
mybatis如何构建和执行的
mybatis的缓存
mybatis的插件系统
mybatis的日志系统
mybatis用到的设计模式
myabtis集成到spring
mybatis集成springboot自动化配置mysql
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎全部的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。算法
上面是mybatis官方介绍,从介绍咱们能够得知mybatis有如下特色:spring
它是一个持久化框架
它支持sql、存储过程、高级映射
它支持手动设置参数而且分装结果集
它支持xml和注解两种配置方式sql
如下为mybatis内的一些基本概念:数据库
SqlSessionFactory:SqlSession类的工厂类
SqlSession:数据库会话类,为用户提供数据库操做方法
Executor:数据库操做的执行器,SqlSession经过Executor操做数据库
MappedStatement:是一个sql操做的抽象
映射接口:具体的业务模块接口,映射接口不须要有实现类,接口内定义了一些列方法,每一个方法对应一个sql操做,方法名就是sql操做的id
映射文件:当配置方式为xml时,能够将sql写在xml配置文件中,一个映射文件对应一个映射接口
Cache:mybatis内部缓存实现
Configuration:全局配置信息(以及配置信息解析的结果)存放处,该实例全局共享,该实例是SqlSessionFactory的属性apache
那mybatis是若是构建和执行的呢,先看一个小例子(这里以xml配置方式为例):设计模式
建立一个maven项目
引入mybatis和mysql链接工具依赖api
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>缓存
编写mybatis配置文件
<?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>
<properties resource="config.properties"/> <settings> <setting name="logImpl" value="LOG4J2"/> <!-- 关闭一级缓存 --> <setting name="localCacheScope" value="STATEMENT"/> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers>
</configuration>
编写映射接口
public interface UserMapper {
List<Map> selectUser();
}
编写映射xml文件(resources/mapper/UserMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test.demos.mybatis.UserMapper">
<select id="selectUser" resultType="java.util.Map"> select * from user </select>
</mapper>
编写启动类
public class App {
public static void main(String[] args) throws Exception { SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); InputStream resource = Resources.getResourceAsStream("config.xml"); SqlSessionFactory sessionFactory = factoryBuilder.build(resource); SqlSession sqlSession = sessionFactory.openSession(); /* 这里经过jdk的动态代理获取UserMapper接口的代理类 */ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<Map> list = userMapper.selectUser(); System.out.println(list.size()); }
}
以上就是搭建纯mybatis运行环境的过程,程序配置过程不详述,这里说一下mybatis的启动构建和执行过程。
先是建立SqlSessionFactoryBuilder实例,改实例的惟一做用就是用来构建SqlSessionFactory的,一但建立了SqlSessionFactory实例SqlSessionFactoryBuilder实例就没用了。构建SqlSessionFactory的过程以下:
加载mybatis配置文件
(XMLConfigBuilder.parse)解析配置文件:解析过程是将xml配置文件内的全部配置标签都解析并包括
<properties/>
<settings/>
<typeAliases/>
<plugins/>
<objectFactory/>
<objectWrapperFactory/>
<reflectorFactory/>
<environments/>
<databaseIdProvider/>
<typeHandlers/>
<mappers/>
解析每一个标签调用不一样的方法处理该标签的配置,例如解析标签是会把内配置的全部映射记录解析将mapper记录添加到Configuration的MapperRegistry中去,而且将对应mapper配置文件里的全部的sql操做解析成MapperStatement(XMLMapperBuilder.parse),同时也会解析resultMap和缓存配置。
解析xml配置文件最终会将全部配置信息放到Configuration实例中去,该实例是全局共享的,后续获取Mapper接口代理、获取MapperStatement、获取Executor都会从这个Configuration实例中获取。
解析完以后建立DefaultSqlSessionFactory实例,这里建立DefaultSqlSessionFactory实例比价简单就是调用一个参数为Configuration的构造函数便可,由于全部的信息都已经存放到Configuration实例中去了
获取SqlSession会话对象,调用SqlSessionFactory.open()方法便可,该方法最终会调用SqlSessionFactory.openSessionFromDataSource方法根据Configuration配置信息建立一个SqlSession实例。
有了SqlSession实例后,获取映射接口的代理类,例如这里的sqlSession.getMapper(UserMapper.class),这里其实就是经过jdk的动态代理获取获得UserMapper接口的代理类,实际代理的InvocationHandler是MapperProxy,在MapperProxy.invoke方法中会拦截映射接口的方法调用,而后建立(可能会被缓存)MapperMethod实例经过执行MapperMethod.execute方法执行sql操做,接着会调用SqlSession内的一系列方法如selectList、insert、query等,根据调用的接口和方法组合的全限定名例如:com.test.UserMapper.getUser来获取MappedStatement,最后经过Executor来做sql的操做(固然其内部也有些封装执行操做,详情可看Executor的实现类BaseExecutor、CachingExecutor的源码)。
Executor执行sql的操做的过程,会将sql执行的结果例如是insert、update、delete操做会返回执行的影响的条数,若是是query操做会将结果封装成对应的sql配置文件配置的类型(如pojo类型、map、resultMap等)返回List或者单个对象并返回。这里mybatis大量使用了范型。
以上就是Mybatis大体的启动构建和执行过程,只能将主要的节点描述,不少细节还需阅读源码。
下图为mybatis启动示意图:
mybatis内置了两种缓存,一种是一级缓存(默认开启),一种是二级缓存(默认开启),一级缓存是会话级别的也就是一个SqlSession实例内的缓存,而二级缓存是namespace级别的,所谓namespace就是一个映射接口的范围,也就是说若是开启了二级缓存那么多个会话若是调用的是同一个映射接口那么是有可能命中二级缓存的。下面详细描述。
一级缓存:在上一部分咱们知道对于SqlSession里的一系列操做方法,实际上最终会调用Executor执行器内的方法来进行sql操做,Executor在mybatis种提供了几个实现类,在不开启二级缓存的状况下默认使用SimpleExecutor实现类,SimpleExecutor是集成的BaseExecutor抽象类,大部分的方法已在BaseExecutor实现,咱们关注BaseExecutor,看成查询操做的时候最终会执行BaseExecutor.query方法,在BaseExecutor类的152行有这样的代码list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;这里就是一级缓存实现的地方,即一级缓存是保存在BaseExecutor内部属性localCache中,而localCache其实就是个PerpetualCache而该类是mybatis缓存的一个实现类,下钻到PerpetualCache内能够发现其内部有个类型为Map的cache属性其中key为CacheKey值就是查询结果。当执行了update、commit等方法后一级缓存会被清空。咱们能够看到,一级缓存只提供了简单的缓存更新的策略,若是使用一个SqlSession实例做同一个查询无论查询多少此其结果都不会变,这就有可能出现脏数据,因此须要斟酌使用一级缓存,若是对数据实时性要求高能够在mybatis配置文件配置标签里设置<setting name="localCacheScope" value="STATEMENT"/>来关闭一级缓存。
二级缓存:二级缓存是默认开启的,若是要关闭能够在mybatis配置文件配置标签里设置<setting name="cacheEnabled" value="false"/>,开启二级缓存后SqlSession内的Executor为CachingExecutor,实际CachingExecutor是使用装饰器模式将包了一层,具体sql操做委托给其余的Executor执行(其实默认是委托给SimpleExecutor),CachingExecutor只作二级缓存的处理。源码CachingExecutor第95行,在执行查询以前先从MappedStatement中获取cache(若是对应mapper映射文件中未配置那么此处的cache是空的,其实这里的cache在mybatis启动构建解析配置文件的时候就已经建立好了,这个cache实例是和namespace一一对应的)。若是部位空那么就从cache中获取值。可是这里不是直接从cache中获取值而是经过CacheExecutor内部的TransactionalCacheManager来获取,之因此这样是为了保证事务成功或失败后缓存的正常保存和清理。例如这里若是开启二级缓存作一次查询其实没发真正保存缓存,此时缓存是保存在TransactionalCache中的,TransactionalCache内保存了全部本次事务操做需有须要缓存的值,只有调用SqlSession.commit方法后将commit传递到TransactionalCache.commit才能真正保存缓存到namespace的cache实例中。在做insert、update、delete时二级缓存也会被清除,想比一级缓存二级缓存有淘汰策略,默认策略上LRU(淘汰最急最少使用),能够在映射配置文件的配置标签中自定义,除此以外还有:
FIFO:先进先出:按对象进入缓存的顺序来移除它们
SOFT:软引用:移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
例如:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
Cache:Cache是mybatis在一二级缓存是对缓存的抽象,Cache接口有一系列的实现类,这些实现类使用装饰器模式来实现对不能缓存功能的包装和功能叠加。
MyBatis 容许你在已映射语句执行过程当中的某一点进行拦截调用。默认状况下,MyBatis 容许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
以上是官方的对plugin的介绍,本质上plugin在sql操做的执行周期中做用的,能够做用的点包括Executor、ParameterHandler、ResultSetHandler、StatementHandler内部的一系列方法。mybatis经过动态代理实现对做用点先后的自定义操做。在Configuration中有个interceptorChain属性,即插件做用链,在Configuration中newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor这些方法都会调用InterceptorChain.pluginAll方法经过动态代理的方式将每一个插件穿起来,生成插件动态代理链是经过插件工具类Plugin来实现,调用Plugin.wrap这个静态方法来建立代理类,代理InvocationHandler类就是Plugin(Plugin自己实现了InvocationHandler接口),固然在建立插件代理类的过程当中还会判断插件类的签名信息即插件类的@Intercepts注解配置信息,该配置信息里配置了该插件的做用点(实际上就是做用的函数调用点)。例如咱们想把查询出来为List<Map>类型的结果内部的Map字段转成驼峰形式(如:user_name转成userName)咱们可使用插件来实现。
@Intercepts({@Signature(
type= ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MyPlugin implements Interceptor {
@Override @SuppressWarnings("unchecked") public Object intercept(Invocation invocation) throws Throwable { List result = (List) invocation.proceed(); if (result != null && result.size() > 0) { if (result.get(0) instanceof Map) { List reList = new ArrayList(); for (Map el : (List<Map>) result) { Map map = new HashMap(); for (String key : (Set<String>) el.keySet()) { map.put(getCamelKey(key), el.get(key)); } reList.add(map); } return reList; } } return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } private String getCamelKey(String key) { String[] split = key.split("_"); String camelKey = ""; for (int i = 0; i < split.length; i++) { if (i != 0) camelKey += split[i].substring(0, 1).toUpperCase() + split[i].substring(1, split[i].length()); else camelKey += split[i]; } return camelKey; }
}
Mybatis 的内置日志工厂提供日志功能,内置日志工厂将日志交给如下其中一种工具做代理:
SLF4J
Apache Commons Logging
Log4j 2
Log4j
JDK logging
实际mybatis只提供了一个日志工厂LogFactory,mybatis经过日志工厂获取日志对象,mybatis自己不提供日志实现,具体的日志交给第三方日志框架来做。能够在mybatis配置文件配置具体日志实现,我门以log4j2为例:
<configuration>
<settings>
<setting name="logImpl" value="LOG4J2"/>
</settings>
</configuration>
配置了mybatis的log实现之后,须要引入相对应的日志依赖包。
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
而后配置日志框架的配置文件(每一个日志框架的配置不一样这里以log4j2为例)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" name="RoutingTest">
<Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console"/> </Root> </Loggers>
</Configuration>
mybatis在实现的时候用了一些设计模式,如:
装饰器模式:在缓存方面Cache缓存接口的各个实现类经过装饰器模式来实现缓存的功能的叠加
动态代理模式:在映射接口代理和插件方面mybatis使用jdk的动态代理模式是为映射接口提供代理类,为插件系统提供代理生成插件链
工厂模式:mybatis为每一个映射接口生成一个代理工厂MapperProxyFactory,每次获取映射接口代理是经过代理工厂获取
组合模式:SqlNode的各个子类使用组合模式实现sql拼接
单例模式:如LogFacotry
模版方法模式:如抽象类BaseExecutor和其子类就是用该模式。模板类定义一个操做中的算法的骨架,而将一些步骤延迟到子类中。使得子类能够不改变一个算法的结构便可重定义该算法的某些特定步骤。
mybatis集成到spring须要添加mybatis-spring依赖,这个依赖包是mybatis和spring对接依赖包。添加spring依赖和mybatis-spring依赖
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.4.RELEASE</version>
</dependency>
<!-- mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version>
</dependency>
配置spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 属性扫描 --> <context:property-placeholder location="config.properties"/> <!-- 组件扫描 --> <context:component-scan base-package="test.demos.mybatis"/> <!-- 数据源 --> <bean id="dataSource" class="com.mysql.cj.jdbc.MysqlDataSource"> <property name="url" value="${url}"/> <property name="user" value="${username}"/> <property name="password" value="${password}"/> <property name="databaseName" value="test"/> </bean> <!-- 配置sqlSessionFactory工厂bean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:config.xml"/> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置sqlSessionTemplate --> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg ref="sqlSessionFactory"/> </bean> <!-- 注册扫描映射接口bean --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="test.demos.mybatis"/> </bean>
</beans>
从spring配置能够知道mybatis-spring主要作了一下几件事:
配置sqlSessionFactory工厂bean,该bean是一个工厂bean(能够理解为这个工厂bean就是SqlSessionFactory的bean,当注入的时候工厂bean会自动点用getObject方法获取获得SqlSessionFactory实例)
配置sqlSessionTemplate会话模版,它是SqlSession的子类,它至关于全局的会话代理类它内部也是经过代理的方式sql操做委托给别的SqlSession。由于它能够做为全局的SqlSession因此它是线程安全的,之因此线程安全的是由于全部经过SqlSessionTemplate调用的诸如selectList、update的方法都会委托给SqlSessionTemplate内部的sqlSessionProxy,而sqlSessionProxy是一个SqlSession的代理,其InvocationHandler是SqlSessionInterceptor,在SqlSessionInterceptor.invoke中每次都会从TransactionSynchronizationManager中获取SqlSession,而在TransactionSynchronizationManager中使用ThreadLocal实现线程安全。(这里大概描述详情看源码SqlSessionTemplate、SqlSessionUtils)
注册扫描映射接口bean:MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,在bean初始化的时候会调用postProcessBeanDefinitionRegistry,MapperScannerConfigurer.postProcessBeanDefinitionRegistry方法内就是扫描注册映射接口bean的过程。扫描注册映射接口后,才能够被注入到其余的Component中。
mybatis集成springboot须要添加一个start
<dependency>
<groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version>
</dependency>
其实mybatis-spring-boot-starter只是个空的依赖,mybatis-spring-boot-starter依赖了mybatis-spring-boot-autoconfigure,主要的代码在这个自动化配置包里。自动化配置依赖会读取mybatis相关的配置属性,而后自动配置咱们上面提到的mybatis相关的组件。配置例子:
mybatis.mapper-locations=classpath:/mapper/*/Mapper.xmlmybatis.typeAliasesPackage=com.test.*.modelmybatis.configuration.map-underscore-to-camel-case=truemybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImplmybatis.configuration.callSettersOnNulls=true这里不将springboot相关内容,只作配置样例介绍。