Mybatis系列一:构建SqlSessionFactory

一、SqlSessionFactoryBuilder

SqlSessionFactoryBuilder的build方法接受Reader/InputStream/Configuration作为配置源,创建SqlSessionFactory实例。在接受Reader/InputStream作为配置源时,创建了XMLConfigBuilder实例来解析XML配置。

 

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {

   try {

     XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);

     return build(parser.parse());

   } catch (Exception e) {

     throw ExceptionFactory.wrapException("Error building SqlSession.", e);

   } finally {

     ErrorContext.instance().reset();

     try {

       reader.close();

     } catch (IOException e) {

       // Intentionally ignore. Prefer previous error.

     }

   }

 }

 

 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {

   try {

     XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

     return build(parser.parse());

   } catch (Exception e) {

     throw ExceptionFactory.wrapException("Error building SqlSession.", e);

   } finally {

     ErrorContext.instance().reset();

     try {

       inputStream.close();

     } catch (IOException e) {

       // Intentionally ignore. Prefer previous error.

     }

   }

 }

 

public SqlSessionFactory build(Configuration config) {

   return new DefaultSqlSessionFactory(config);

 }

 

二、XMLConfigBuilder

2.1 类图

image.png

 

2.2 解析Mybatis配置 parse

settings、mappers元素的解析稍微复杂些。

public Configuration parse() {

   if (parsed) {

     throw new BuilderException("Each XMLConfigBuilder can only be used once.");

   }

   parsed = true;

   parseConfiguration(parser.evalNode("/configuration"));

   return configuration;

 }

 

2.3 解析configuration元素 parseConfiguration

Mybatis配置文件解析流水线式按部就班的解析,各元素解析逻辑分散到独立方法中,清晰了然。

 private void parseConfiguration(XNode root) {

   try {

     // 解析properties元素并设置到Configuration对象

     propertiesElement(root.evalNode("properties"));

// 解析settings元素保存到Properties中

     Properties settings = settingsAsProperties(root.evalNode("settings"));

     loadCustomVfs(settings);

// 解析typeAliases元素并设置到Configuration对象

     typeAliasesElement(root.evalNode("typeAliases"));

// 解析plugins元素并设置到Configuration对象

     pluginElement(root.evalNode("plugins"));

// 解析objectFactory元素并设置到Configuration对象

     objectFactoryElement(root.evalNode("objectFactory"));

// 解析objectWrapperFactory元素并设置到Configuration对象

     objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

// 解析reflectorFactory元素并设置到Configuration对象

     reflectorFactoryElement(root.evalNode("reflectorFactory"));

     // settings配置值保存到Configuration对象

     settingsElement(settings);

     // 解析environments元素默认environment并设置到Configuration对象

     environmentsElement(root.evalNode("environments"));

// 解析databaseIdProvider元素并设置到Configuration对象

     databaseIdProviderElement(root.evalNode("databaseIdProvider"));

// 解析typeHandlers元素并设置到Configuration对象

     typeHandlerElement(root.evalNode("typeHandlers"));

// 解析mappers元素并设置到Configuration对象

     mapperElement(root.evalNode("mappers"));

   } catch (Exception e) {

     throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

   }

 }

 

2.4 解析properties元素

<properties resource="jdbc.properties">

<property name="jdbc.username" value="username"/>

<property name="hello" value="hello"/>

</properties>

 

2.4.1 解析流程

(1) 解析properties子元素保存到Properties中

(2) 获取properties的resource或url属性值指定的外部配置,设置到(1)中,同名覆盖

(3) 创建XMLConfigBuilder或SqlSessionFactory实例时如果指定了Properties,设置到(1)中,同名覆盖

 

存在这样一个优先级顺序:properties子元素 < properties的resource或url属性 < 参数Properties

 

2.4.2 propertiesElement

private void propertiesElement(XNode context) throws Exception {

   if (context != null) {

// 获取properties子元素保存到Properties中

     Properties defaults = context.getChildrenAsProperties();

     // properties的resource、url属性值

     String resource = context.getStringAttribute("resource");

     String url = context.getStringAttribute("url");

// properties的resource、url属性不能同时配置

     if (resource != null && url != null) {

       throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");

     }

 

// 根据properties的resource、url属性值获取外部配置,保存到defaults中,同名属性会覆盖

     if (resource != null) {

       defaults.putAll(Resources.getResourceAsProperties(resource));

     } else if (url != null) {

       defaults.putAll(Resources.getUrlAsProperties(url));

     }

 

// 创建XMLConfigBuilder或SqlSessionFactory实例时如果指定了Properties,设置到Configuration的variables属性上

     Properties vars = configuration.getVariables();

     if (vars != null) {

       defaults.putAll(vars);

     }

     parser.setVariables(defaults);

     configuration.setVariables(defaults);

   }

 }

 

2.5 解析settings元素

<settings>

<setting name="cacheEnabled" value="true"/>

<setting name="lazyLoadingEnabled" value="true"/>

<setting name="autoMappingBehavior" value="PARTIAL"/>

</settings>

 

2.5.1 解析流程

(1) 获取settings子元素保存到Properties中

(2) 获取Configuration类的所有属性、getter和setter方法

(3) 检查setting(name属性值)在Configuration类中是否存在,不存在抛异常

2.5.2 settingsAsProperties

private Properties settingsAsProperties(XNode context) {

   if (context == null) {

     return new Properties();

   }

   //  获取settings子元素保存到Properties中

   Properties props = context.getChildrenAsProperties();

   // 检查配置类是否知道所有设置,MetaClass后续分析。传送门《Mybatis系列二:MetaClass解析Configuration元数据》

   MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);

   for (Object key : props.keySet()) {

// 配置的setting(name属性值)不是Configuration的属性,则抛出异常

     if (!metaConfig.hasSetter(String.valueOf(key))) {

       throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");

     }

   }

   return props;

 }

 

2.6 解析typeAliases元素

配置包名,让MyBatis去扫描包中的类,并根据类名得到相应的别名。可配合@Alias注解使用,不让MyBatis按照默认规则生成别名。传送门《Mybatis系列三:类型别名、类型解析器的注册》

<typeAliases>

<package name="com.lemon.entity"/>

<package name="com.lemon.service/>

</typeAliases>

 

明确为某个类配置别名

<typeAliases>

<typeAlias type="com.lemon.entity.Movie" />

<typeAlias alias="author" type="com.lemon.entity.Ideal" />

</typeAliases>

 

2.6.1 解析流程

(1) package子元素:扫描指定包下的类,可结合@Alias注解指定别名,或者按默认规则生成别名

(2) typeAlias子元素:按照配置为类设置别名,未指定按默认规则生成别名

2.6.2 typeAliasesElement

private void typeAliasesElement(XNode parent) {

   if (parent != null) {

     for (XNode child : parent.getChildren()) {

       // 扫描指定包下的类,可结合@Alias注解指定别名,或者按默认规则生成别名

       if ("package".equals(child.getName())) {

         String typeAliasPackage = child.getStringAttribute("name");

         configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);

       } else {

    // 明确为某个类配置别名

         String alias = child.getStringAttribute("alias");

         String type = child.getStringAttribute("type");

         try {

           Class<?> clazz = Resources.classForName(type);

           if (alias == null) {

             typeAliasRegistry.registerAlias(clazz);

           } else {

             typeAliasRegistry.registerAlias(alias, clazz);

           }

         } catch (ClassNotFoundException e) {

           throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);

         }

       }

     }

   }

 }

 

2.7 解析plugins元素

2.7.1 plugin介绍

通过插件机制我们可在SQL执行过程中的某些点上做一些自定义操作。实现一个插件首先需要让插件类实现 Interceptor接口,然后在插件类上添加@Intercepts 和@Signature 注解,用于指定想要拦截的目标方法。 MyBatis 允许拦截下面接口中的一些方法:

Executor: update, query, flushStatements, commit, rollback, getTransaction,close, isClosed

ParameterHandler: getParameterObject, setParameters

StatementHandler: prepare, parameterize, batch, update, query

ResultSetHandler: handleResultSets, handleOutputParameters

 

<plugins>

<plugin interceptor="com.lemon.plugin.ExamplePlugin">

<property name="key" value="value"/>

</plugin>

</plugins>

 

2.7.2 解析流程

(1) 获取interceptor的子元素保存到Properties中

(2) 实例化interceptor并设置属性(1)

 

2.7.3 pluginElement

private void pluginElement(XNode parent) throws Exception {

   if (parent != null) {

     for (XNode child : parent.getChildren()) {

       String interceptor = child.getStringAttribute("interceptor");

       Properties properties = child.getChildrenAsProperties();

       Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();

       interceptorInstance.setProperties(properties);

       configuration.addInterceptor(interceptorInstance);

     }

   }

 }

 

2.8 解析objectFactory元素

private void objectFactoryElement(XNode context) throws Exception {

   if (context != null) {

     String type = context.getStringAttribute("type");

     Properties properties = context.getChildrenAsProperties();

     ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();

     factory.setProperties(properties);

     configuration.setObjectFactory(factory);

   }

 }

 

2.9 解析objectWrapperFactory元素

private void objectWrapperFactoryElement(XNode context) throws Exception {

   if (context != null) {

     String type = context.getStringAttribute("type");

     ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();

     configuration.setObjectWrapperFactory(factory);

   }

 }

 

2.10 解析reflectorFactory元素

private void reflectorFactoryElement(XNode context) throws Exception {

   if (context != null) {

      String type = context.getStringAttribute("type");

      ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();

      configuration.setReflectorFactory(factory);

   }

 }

 

2.11 解析environments元素

private void environmentsElement(XNode context) throws Exception {

   if (context != null) {

     // 创建XMLConfigBuilder或SqlSessionFactory实例时没有指定environment,获取默认environment

     if (environment == null) {

       environment = context.getStringAttribute("default");

     }

     for (XNode child : context.getChildren()) {

       String id = child.getStringAttribute("id");

  // 判断是不是指定的Environment

       if (isSpecifiedEnvironment(id)) {

    // 获取事务工厂 JDBC/MANAGED

         TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));

    // 获取数据源工厂 JNDI/POOLED/UNPOOLED

         DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));

         DataSource dataSource = dsFactory.getDataSource();

         // 根据数据源和事务构建Environment

         Environment.Builder environmentBuilder = new Environment.Builder(id)

             .transactionFactory(txFactory)

             .dataSource(dataSource);

         configuration.setEnvironment(environmentBuilder.build());

       }

     }

   }

 }

 

2.12 解析databaseIdProvider元素

2.4.1 解析流程

 

2.4.2 propertiesElement

// 默认注册了类别名DB_VENDOR --> VendorDatabaseIdProvider

private void databaseIdProviderElement(XNode context) throws Exception {

   DatabaseIdProvider databaseIdProvider = null;

   if (context != null) {

     String type = context.getStringAttribute("type");

     // awful patch to keep backward compatibility

     if ("VENDOR".equals(type)) {

         type = "DB_VENDOR";

     }

     Properties properties = context.getChildrenAsProperties();

     databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();

     databaseIdProvider.setProperties(properties);

   }

   Environment environment = configuration.getEnvironment();

   if (environment != null && databaseIdProvider != null) {

     // 获取元数据得到数据库标识

     String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());

     configuration.setDatabaseId(databaseId);

   }

 }

 

2.13 解析typeHandlers元素

<!-- 自动扫描 -->

<typeHandlers>

<package name="com.lemon.handlers"/>

</typeHandlers>

 

<!-- 手动配置 -->

<typeHandlers>

<typeHandler jdbcType="TINYINT"

javaType="com.lemon.handlers.Movie"

handler="com.lemon.handlers.MovieTypeHandler"/>

</typeHandlers>

 

传送门《Mybatis系列三:类型别名、类型解析器的注册》

private void typeHandlerElement(XNode parent) throws Exception {

   if (parent != null) {

     for (XNode child : parent.getChildren()) {

       if ("package".equals(child.getName())) {

         String typeHandlerPackage = child.getStringAttribute("name");

         typeHandlerRegistry.register(typeHandlerPackage);

       } else {

         String javaTypeName = child.getStringAttribute("javaType");

         String jdbcTypeName = child.getStringAttribute("jdbcType");

         String handlerTypeName = child.getStringAttribute("handler");

         Class<?> javaTypeClass = resolveClass(javaTypeName);

         JdbcType jdbcType = resolveJdbcType(jdbcTypeName);

         Class<?> typeHandlerClass = resolveClass(handlerTypeName);

    // 如果未配置javaType、jdbcType,获取TypeHandler类上@MappedTypes、@MappedJdbcTypes注解

         if (javaTypeClass != null) {

           if (jdbcType == null) {

             typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);

           } else {

             typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);

           }

         } else {

           typeHandlerRegistry.register(typeHandlerClass);

         }

       }

     }

   }

 }

 

2.14 解析mappers元素

<mappers>

<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>

<mapper url="file:///var/mappers/AuthorMapper.xml"/>

<mapper class="org.mybatis.builder.AuthorMapper"/>

<package name="org.mybatis.builder"/>

</mappers>

 

private void mapperElement(XNode parent) throws Exception {

   if (parent != null) {

     for (XNode child : parent.getChildren()) {

       if ("package".equals(child.getName())) {

    // 扫描指定包下的Mapper

         String mapperPackage = child.getStringAttribute("name");

    // 传送门《Mybatis系列五:MapperAnnotationBuilder解析Mapper Class》

         configuration.addMappers(mapperPackage);

       } else {

    // 获取resource、url、class属性,只能设置其中一个否则抛异常

         String resource = child.getStringAttribute("resource");

         String url = child.getStringAttribute("url");

         String mapperClass = child.getStringAttribute("class");

         if (resource != null && url == null && mapperClass == null) {

           ErrorContext.instance().resource(resource);

           InputStream inputStream = Resources.getResourceAsStream(resource);

// 传送门《Mybatis系列四:XMLMapperBuilder解析XL配置文件》

           XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());

           mapperParser.parse();

         } else if (resource == null && url != null && mapperClass == null) {

           ErrorContext.instance().resource(url);

           InputStream inputStream = Resources.getUrlAsStream(url);

// 传送门《Mybatis系列四:XMLMapperBuilder解析XL配置文件》

           XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());

           mapperParser.parse();

         } else if (resource == null && url == null && mapperClass != null) {

           Class<?> mapperInterface = Resources.classForName(mapperClass);

           // 获取Mapper注册到Configuration的MapperRegistry属性中

// 传送门《Mybatis系列五:MapperAnnotationBuilder解析Mapper Class》

           configuration.addMapper(mapperInterface);

         } else {

           throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");

         }

       }

     }

   }

 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值