笔记学习自MyBatis技术内幕,作者的水平很高,虽然书里表面说的MyBatis源码,但实际上是谈论设计模式的具体应用,强推!
基础支持
类型转换
Java类型到Jdbc类型是怎么实现的?
MyBatis的类型转换器依赖于TypeHandler
接口,其中getResult
负责将Java类型转换成JdbcType类型,而setParameter
则反之
内置的抽象类类为BaseTypeHandler
,所有默认实现类都继承于它,基本是调用PreparedStatement
和ResultSet
方法来具体实现参数绑定/获取指定列值
TypeHandlerRegistry
负责管理MyBatis初始化阶段创建的所有已知TypeHandler
对象
具体的是提供映射(真·Map对象),核心字段有
JDBC_TYPE_HANDLER_MAP:用于表示JdbcType
到TypeHandler
的唯一映射
TYPE_HANDLER_MAP
:用于表示Type
到多个JdbcType
的TypeHandler
的映射(比如String会对应于char和varchar)
比如类型为Map<JdbcType,TypeHandler<?>>
的JDBC_TYPE_HANDLER_MAP
字段(实质为EnumMap
),当从结果集获取数据时,依靠这个还实现Jdbc类型到Java类型的转换
而关键的register
的常见重载方法有(Type,JdbcType,TypeHandler)
的入参,具体注册顺序为
1.检测javaType是否为空,无论是否为空均加入到ALL_TYPE_HANDLERS_MAP
(key为handler.getClass()
)
2.如果非空,从TYPE_HANDLER_MAP
中通过javaType获得对应的Handler的Map
3.如果没有对应Handler的Map,则通过直接新建一个空的HashMap并加入到TYPE_HANDLER_MAP
4.在对应的handler的Map加入(jdbcType,handler)
键值对
这样实现了单个javaType对应多个jdbcType的handler映射注册,在此之后要获得typeHandler可通过getTypeHandler(type,jdbcType)
得到,其实就是跑上面的Map的对应键值即可
类型别名的实现?
MyBatis通过TypeAliasRegistry
完成表/列名的别名注册和管理
具体是通过Map<String,Class<?>> TYPE_ALIASES
字段以及registerAlias()
实现,就是个get检查有没有然后再put进去的过程(冲突会抛出异常!)。。
日志
MyBatis通过适配器模式实现对不同的日志接口/实现(Adaptee
)的兼容,其内部使用接口为Log
(适配器中的Target
),由LogFactory
来实现适配过程(Adapter)
资源加载
MyBatis封装了ClassLoaderWrapper
作为ClassLoader
的包装器,使用起来和ClassLoader
一致,但可调整ClassLoader
使用顺序(多个ClassLoader依次检查,并从中挑出可用的)
该类中有两个字段defaultClassLoader
和systemClassLoader
,前者由系统指定,后者由ClassLoader.getSystemClassLoader()
提供
该ClassLoaderWrapper中的getClassLoaders(classLoader)
返回ClassLoader[]
数组,分别是
classLoader / defaultClassLoader / Thread.currentThread().getContextClassLoader() / getClass().getClassLoader() / systemClassLoader
不同的类加载器具有不同的访问权限,对于获取资源如URL getResourceAsURL(resource,classLoader[])
就会依据上面给出的类加载器顺序依次获取资源(失败后会再通过"/"+resource再次尝试)
DataSource
数据源组件需要通过实现javax.sql.DataSource
接口来实现,MyBatis通过经典的工厂模式来提供两个实现类PooledDataSource
和UnpooledDataSource
,其对应的工厂类为实现DataSourceFactory
的PooledDataSourceFactory
和UnpooledDataSourceFactory
,其工厂接口(关注创建API)-工厂实现(关注创建逻辑)-产品接口(关注用户API)-产品实现(关注业务逻辑)的设计典范是十分规范的面向接口编程
在实现类中均封装好的注册JDBC驱动、建立Connection等模板
PooledDataSource
不直接管理javax.sql.Connection
对象,而是委托给PooledConnection
,它直接封装了Connection
对象及其代理(比方说当调用代理的close()时实际是调用PooledDataSource.pushConnection()
)
并且由PooledState
通过ArrayList<PooledConnection>
管理idle的连接
事务管理
似乎没啥好说的?也是个工厂实现
缓存
Cache
接口提供非常多的缓存实现装饰器,比如SoftCache
、LruCache
、SynchronizedCache
Cache
中指定的key CacheKey
不是一个简单的String,内部封装了multiplier
/hashCode
/checkSum
/count
等用于重写hashCode()
和equals()
的字段,还有List<Object> updateList
会记录多种影响缓存项的因素(既可通过多个对象组成一个key),比如MappedStatement
的id/查询结果集的范围/查询使用的SQL/传递到SQL的参数,每次update(obj)到一个key时会判断是否是数组,如果是则添加多个内部对象到cacheKey,不管如何都是通过doUpdate(obj)
来具体更新,四个字段更新:count++,checkSum+=baseHashcode,baseHashCode*=count,hashCode=multiplier*hashCode+baseHashCode
,最后把obj放入到updateList中,判断key是否相等则依次按照上述字段进行对比,迫不得已才用updateList的元素比较
Binding
Binding实现xml/注解到接口的绑定(就是一堆Mapper就能实现执行SQL的原理)
从而实现SQL到Java层的对接
XxxMapper mapper = session.getMapper(XxxMapper.class);
Xxx obj = mapper.do(...);
POJO到mapper的实现?
就是个面向接口的动态代理,因此写mapper时声明interaface
解析器
解析器指对XML的处理,MyBatis使用DOM解析方式,具体使用到XPath解析并将其封装到XPathParser
类
反射模块
MyBatis提供封装好的reflection
包
核心处理
配置解析
MyBatis初始化工作为加载并解析mybatis-config.xml
配置文件、映射配置文件和相关的注解
其初始化入口为SqlSessionFactoryBuilder.build(reader,enviroonment,properties)
具体的会先尝试创建XMLConfigBuilder parser
,如果成功则委托给build(parser.parse())
XMLConfigBuilder
就是一个BaseBuilder
抽象类的具体实现
BaseBuilder
包含一个全局唯一配置configuration
,别名标记typeAliasRegistry
,handler注册中心typeHandlerRegistry
,后面两个在前面已经讨论过细节
外部API流程
1.通过配置文件生成SqlSessionFactory对象,SqlSessionFactory生成SqlSession(openSession())
2.SqlSession接口定义执行SQL所需的方法
3.通过SqlSession对象执行配置中的SQL语句
4.通过SqlSession对象提交事务
5.关闭SqlSession