开心一刻html
本人幼教老师,冬天戴帽子进教室,被小朋友看到,这时候,有个小家伙对我说:老师你的帽子太丑,赶忙摘了吧。我逗他:那你好好学习,之后给老师买个漂亮的?这孩子想都没想马上回答:等我赚钱了,带你去韩国整形git
咱们先来看一个纯粹的mybatis示例(不集成spring等其余框架),代码很简单,结构以下spring
完整代码地址:mybatis;mapper层和咱们平时说的dao层指的是同一个内容,都是数据库操做的封装,可是在没有集成mybatis时,dao层的接口都是须要咱们手动去写其实现类,可在上图中咱们却发现:咱们并无手动去实现PersonMapper接口,但工程却能实实在在的查询数据库,获取咱们须要的数据,以下图所示sql
从上图咱们发现,PersonMapper实例是一个代理对象,咱们操做的实际上是PersonMapper的代理实现;也就是说不用咱们手动去实现PersonMapper接口,mybatis会动态生成PersonMapper的代理实例,而后由代理实例完成数据库的操做数据库
那么问题来了,mybatis是什么时候、何地、如何生成mapper代理实例的呢?咱们接着往下看设计模式
针对上述问题,咱们来跟下mybatis源码springboot
XMLConfigBuilder解析Mybatis配置文件(mybatis-config.xml),将配置文件中各个属性解析到Configuration实例中,而后以Configuration实例构建SqlSessionFactory(实际是DefaultSqlSessionFactory);其中parseConfiguration方法是解析的具体过程,有兴趣的能够更深一步的去探究mybatis
/** * root是以configuration标签开始的文档树 * 解析配置文件中的各个标签,并存放到Configuration实例对应的属性中 * 解析完成以后,配置文件中的内容所有解析到了Configuration实例中 * @param root */ private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); // 解析配置文件中的properties标签 Properties settings = settingsAsProperties(root.evalNode("settings")); // 解析配置文件中的settings标签 loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); // 解析配置文件中的typeAliases标签 pluginElement(root.evalNode("plugins")); // 解析配置文件中的plugins标签 objectFactoryElement(root.evalNode("objectFactory")); // 解析配置文件中的objectFactory标签 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析配置文件中的objectWrapperFactory标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 解析配置文件中的reflectorFactory标签 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); // 解析配置文件中的environments标签 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析配置文件中的databaseIdProvider标签 typeHandlerElement(root.evalNode("typeHandlers")); // 解析配置文件中的typeHandlers标签 mapperElement(root.evalNode("mappers")); // 解析配置文件中的mappers标签 } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
上述代码中的mapperElement(root.evalNode("mappers"));是否是很诱人?与咱们的mapper有关系,是否是在这里就生成了mapper的代理实例,仍是只是读取了mapper配置文件的内容?暂时还不敢确定,那么咱们跟进去看看app
其中有两个方法值得重点关注下,具体以下,里面的注释能够重点看下,有兴趣的能够更进一步的跟进去框架
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); // 解析映射文件Person.xml configuration.addLoadedResource(resource); bindMapperForNamespace(); // 将mapper与namespace绑定起来; 将PersonMapper接口与MapperProxyFactory关联起来 } parsePendingResultMaps(); // 解析Configuration的incompleteResultMaps到Configuration的resultMaps parsePendingCacheRefs(); // 解析Configuration的incompleteCacheRefs到Configuration的cacheRefMap parsePendingStatements(); // 解析Configuration的incompleteStatements到Configuration的mappedStatements } /** * context是映射文件:Person.xml的文档树,以mapper标签开始 * 解析映射文件中的各个标签,并存放到MapperBuilderAssistant实例对应的属性中 */ private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); // 解析mapper标签的namespace属性 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); // namespace属性值解析到Configuration的mapperRegistry中 cacheRefElement(context.evalNode("cache-ref")); // 解析cache-ref标签到Configuration的cacheRefMap中 cacheElement(context.evalNode("cache")); // 解析cache标签到Configuration的caches中 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析parameterMap标签到Configuration的parameterMaps中 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析resultMap标签到Configuration的resultMaps中 sqlElement(context.evalNodes("/mapper/sql")); // 解析sql标签到XMLMapperBuilder的sqlFragments中 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 解析select|insert|update|delete标签到Configuration的mappedStatements中 } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
此时SqlSessionFactory已经建立,但PersonMapper的代理实例尚未建立;期间准备了不少东西,包括读取配置文件和映射文件的内容,并将其放置到Configuration实例的对应属性中
实例化了Transaction(JdbcTransaction)、Executor(SimpleExecutor)和SqlSession(DefaultSqlSession),此时mapper代理实例仍未被建立
能够看到,最终仍是利用了JDK的动态代理
protected T newInstance(MapperProxy<T> mapperProxy) { // 利用JDK的动态代理生成mapper的代理实例 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
生成了mapper的代理实例,后续就能够利用此代理实例进行数据库的操做了
更多动态代理的信息请查看:设计模式之代理,手动实现动态代理,揭秘原理实现
一、咱们用mytabis操做数据库,有一个固定流程:先建立SqlSessionFactory,而后建立SqlSession,而后再建立获取mapper代理对象,最后利用mapper代理对象完成数据库的操做;一次数据库操做完成后须要关闭SqlSession;
二、建立SqlSessionFactory实例的过程当中,解析mybatis配置文件和映射文件,将内容都存放到Configuration实例的对应属性中;建立SqlSession的过程当中,有建立事务Transaction、执行器Executor,以及DefaultSqlSession;Mapper代理对象的建立,利用的是JDK的动态代理,InvocationHandler是MapperProxy,后续Mapper代理对象方法的执行都会先通过MapperProxy的invoke方法;
三、不少细节没有讲到,但大致流程就是这样;另外提下,实际应用中,mybatis每每不会单独使用,绝大多数都是集成在spring中;关于在spring的集成下,mapper代理对象的建立过程查看:springboot集成下,mybatis的mapper代理对象到底是如何生成的