Mybatis
Mybatis 是一个半自动的orm框架,封装了jdbc的通用操作,创建链接、加载驱动、事务的提交回滚等、可以让程序员专注于sql的执行。
整体架构
sqlSession -- 顶层api接口, 负责与用户进行交互,定义了增删改查等接口 。
Executor -- mybatis 的核心, 负责查询缓存维护, 最终sql生成, 以及调用statement访问数据库
statementHandler -- mybatis与jdbc的交互封装, 包括设置参数和返回值处理
parameterHandler 与 resultSetHandler -- 负责设置参数及返回数据
Statement -- jdbc statement
mappedStatement -- 一条sql语句会对应一个ms, 里面包含了本sql的全部信息, 包括param ,result配置,解析后的sqlnode等。
执行流程
- 启动时会以手动注册或扫描的方式扫描所有mapper文件, 根据class type给文件创建代理对象,进入invoke方法后调用MapperMethod, 它定义了sql的类型和一些基本参数, 根据sql类型不同,调用sqlSession不同的方法。
- 通过sqlSessionFactory 创建 sqlSession, 外观模式,默认为defaultSqlSession, 成员变量有configuration(大管家维护所有元数据),executor具体执行器。
- 在构建sqlSessionFactory的时候, 就通过sqlSessionFactoryBuilder的方式解析了所有的mapper及包含的sql语句, 解析后的结果为mapperStatement,一条sql语句对应一个mappedStatement, mappedStatement 存储着语句的基本信息,param配置和result配置等,id为命名空间+id,还有sqlSource 存储着sql 解析后的结果,动态sql为mixedNode树结构,和参数一块生成最终sql。
- sqlSessionFactory 执行时,会从configuration中获取到该方法对应的mappedstatement,交于executor具体执行,
- Executor 会先判断缓存中是否存在, 存在直接返回, 不存在执行数据库查询。
- 执行数据库查询时,会根据statement类型生成statementHandler, 执行参数替换等操作,然后发送给数据库具体执行,执行完成后根据resultSetHandler 处理返回值转换为pojo对象。
- excecutor处理缓存相关操作。
缓存机制
Mybatis 提供了缓存机制来减少数据库的查询。提供了二级缓存机制来应对不同的使用场景。 一是sql session 级别, 另一个是namespeace级别。
sql session 级别具体是在每个sql session的executor中内置了一个hashmap的localCache对象, 每次查询都会优先从此处查, 查询到就不查询数据库了。 缺点是虽然单个session 内部执行更新语句时会清除缓存, 但多个session 之间会出现脏读情况,默认的session缓存是不可关闭的, 但可以将session 缓存的级别设置为 statement, 这样每次语句执行都会清除缓存,等于不使用缓存。
Name space 级别是给每个mapper创建了一个缓存,使用的是mappedstatement中的cache对象来实现的, 采用了装饰链模式来扩展功能, 如果xml文件中配置了cache标签或者使用了useNameSaceCache注解,在创建mappedstatement时就会将此变量初始化, mappedstatement是语句级别的, 为什么说是namespace级别的, 因为configuration里面有个map,key为namespace id, value为cache, 每个mappedstatement cache创建时会拿自己的namespace id去查找是否创建了cache, 如果创建过就直接赋值。
Cache 是一个通用接口, 定义了getObject、putObject等通用方法, mybatis提供了多种实现方案,我们也可以自己实现cache接口然后引入cache中, 在mappedstatement初始化阶段, 在解析cachenamespace 阶段, 会先创建base cache,然后根据要添加的功能,依次使用装饰器方式添加, 每个cache维护一个装饰对象,在自己的逻辑执行中执行装饰对象的方法,完成对装饰方法的扩展。 Mybatis 提供了 lru cache 、 log cache、block cache等多种功能。
不建议使用缓存, 现在大多数服务都不是单体应用, 每个服务维护一套缓存很容易导致不一致问题, 如果非用缓存不可, 可以考虑分布式缓存。
插件机制
Mybatis 的插件机制主要是通过动态代理拦截链的方式实现功能的增强,典型的像page helper 就是通过插件机制实现的。 Mybatis 可以对四大核心对象生成代理, 分别是 executor、statementHandler、parameterHandler和resultSethandler。
具体的执行流程为:以xml配置或注解的方式收集拦截器列表, 在四大核心对象创建时,会执行pluginAll 方法, 使用拦截链依次对目标对象生成代理(jdk动态代理)。 具体是调用plugin 方法,插件的实现者在此方法内部根据目标对象的类型判断要不要生成代理,生成代理的实现为Plugin的warp方法。 当方法执行时,会进入代理方法中, 实现invocationHandler的也是Plugin类,Plugin 类是实现插件的核心, 其中有三个关键参数, 一是代理对象, 二是拦截器对象,三是需要进行拦截的参数。 进入代理方法后, 会判断当前方法是否为需要拦截的方法,如果是就使用拦截器进行拦截。
pageHelper 插件主要是对executor的query方法进行拦截, 在pageHelper startpage时,会将分页参数封装为Page对象后存在ThreadLocal中。 当方法拦截后,会从ThreadLocal中取出page参数,重写boundSql,在尾部添加limit参数, 当执行结束后,remove掉ThreadLocal中的数据。 所以pagehelper只对start后的第一个query语句有效,而且使用的是普通ThreadLocal,对线程池调用也会失效
常见标签
resultMap , paramMap, cache, selectKey, sql,include
choose,if, where ,when,then,trim
常见设计模式
zhuanlan.zhihu.com/p/655041666
建造者模式, xmlbuilder, xmlmapperbuilder,xmlstatementbuilder,在mybatis初始化的过程中,大量使用的建造者模式, 一般多参构造函数会使用建造者模式。
工厂模式,sqlSessionFactory, openSession方法,会根据入参不同创建不同的sqlSession对象
代理模式,主要有两个地方代理用的较多, 都是使用的jdk动态代理, 一个是mapper的绑定逻辑,一个是插件的执行。mapperProxy和Plugin
装饰器模式,mybatis 的二级cache使用了装饰器模式, 给一个基础的cache接口逐步装饰各种能力。 接口为cache,每个实现类都持有着装饰对象和自己的缓存逻辑, 在执行装饰对象的逻辑前后增加功能,比较典型的有logcache,统计命中率等,lrucache,scheduleCache,synchronizedCache等。
模版方法,executor的baseExecutor 实现采用了模版方法模式, 定义了缓存key获取,清除缓存等主体流程,具体的执行方法下沉到子类executor执行。
常见问题
#和的区别, 一个是字符串替换, 一个值文本替换, #是预编译,在解析时会解析为占位符?在parameterHandler会使用参数依次替换, 以文本方式替换, 字符串会带引号标识,能有效防止sql注入。 而是文本直接替换, 无引号,在一些动态order by场景需要使用,有sql注入风险,写代码需谨慎