阅读 302

Mybatis源码分析(mybatis源码解析)

MyBatis介绍

MyBatis是一个持久层的ORM框架,使用简单,学习成本较低。可以执行自己手写的SQL语句,比较灵活。但是MyBatis的自动化程度不高,移植性也不高,有时从一个数据库迁移到另外一个数据库的时候需要自己修改配置,所以称只为半自动ORM框架。

Mybaits整体体系图

image.png

例子

public class App {     public static void main(String[] args) {         String resource = "mybatis-config.xml";         Reader reader;         try {             //将XML配置文件构建为Configuration配置类             reader = Resources.getResourceAsReader(resource);             // 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory             SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);             // 数据源 执行器  DefaultSqlSession             SqlSession session = sqlMapper.openSession();             try {                 // 执行查询 底层执行jdbc                 //User user = (User)session.selectOne("com.xxx.mapper.selectById", 1);                 UserMapper mapper = session.getMapper(UserMapper.class);                 System.out.println(mapper.getClass());                 User user = mapper.selectById(1L);                 System.out.println(user.getUserName());             } catch (Exception e) {                 e.printStackTrace();             }finally {                 session.close();             }         } catch (IOException e) {             e.printStackTrace();         }     } } 复制代码

build方法

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.     }   } } 复制代码

image.png
可以看到除了XMLConfigBuilder继承于BaseBuilder,还有一些其他的解析配置文件的类。

XMLConfigBuilder.parse方法

public Configuration parse() {   /**    * 若已经解析过了 就抛出异常    */   if (parsed) {     throw new BuilderException("Each XMLConfigBuilder can only be used once.");   }   /**    * 设置解析标志位    */   parsed = true;   /**    * 解析我们的mybatis-config.xml的    * 节点    * <configuration>    *    *    * </configuration>    */   parseConfiguration(parser.evalNode("/configuration"));   return configuration; } 复制代码

parseConfiguration方法

private void parseConfiguration(XNode root) {   try {     /**      * 解析 properties节点      *     <properties resource="mybatis/db.properties" />      *     解析到org.apache.ibatis.parsing.XPathParser#variables      *           org.apache.ibatis.session.Configuration#variables      */     propertiesElement(root.evalNode("properties"));     /**      * 解析我们的mybatis-config.xml中的settings节点      * 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings      * <settings>           <setting name="cacheEnabled" value="true"/>           <setting name="lazyLoadingEnabled" value="true"/>          <setting name="mapUnderscoreToCamelCase" value="false"/>          <setting name="localCacheScope" value="SESSION"/>          <setting name="jdbcTypeForNull" value="OTHER"/>           ..............          </settings>      *      */     Properties settings = settingsAsProperties(root.evalNode("settings"));     /**      * 基本没有用过该属性      * VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。        Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序        解析到:org.apache.ibatis.session.Configuration#vfsImpl      */     loadCustomVfs(settings);     /**      * 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。      * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING      * 解析到org.apache.ibatis.session.Configuration#logImpl      */     loadCustomLogImpl(settings);     /**      * 解析我们的别名      * <typeAliases>          <typeAlias alias="Author" type="cn.xxx.pojo.Author"/>       </typeAliases>      <typeAliases>         <package name="cn.xxx.pojo"/>      </typeAliases>      解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases      */     typeAliasesElement(root.evalNode("typeAliases"));     /**      * 解析我们的插件(比如分页插件)      * mybatis自带的      * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)        ParameterHandler (getParameterObject, setParameters)        ResultSetHandler (handleResultSets, handleOutputParameters)        StatementHandler (prepare, parameterize, batch, update, query)       解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors      */     pluginElement(root.evalNode("plugins"));     /**      * todo      */     objectFactoryElement(root.evalNode("objectFactory"));     objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));     reflectorFactoryElement(root.evalNode("reflectorFactory"));     // 设置settings 和默认值     settingsElement(settings);     // read it after objectFactory and objectWrapperFactory issue #631     /**      * 解析我们的mybatis环境        <environments default="dev">          <environment id="dev">            <transactionManager type="JDBC"/>            <dataSource type="POOLED">            <property name="driver" value="${jdbc.driver}"/>            <property name="url" value="${jdbc.url}"/>            <property name="username" value="root"/>            <property name="password" value="Zw726515"/>            </dataSource>          </environment>        <environment id="test">          <transactionManager type="JDBC"/>          <dataSource type="POOLED">          <property name="driver" value="${jdbc.driver}"/>          <property name="url" value="${jdbc.url}"/>          <property name="username" value="root"/>          <property name="password" value="123456"/>          </dataSource>        </environment>      </environments>      *  解析到:org.apache.ibatis.session.Configuration#environment      *  在集成spring情况下由 spring-mybatis提供数据源 和事务工厂      */     environmentsElement(root.evalNode("environments"));     /**      * 解析数据库厂商      *     <databaseIdProvider type="DB_VENDOR">               <property name="SQL Server" value="sqlserver"/>               <property name="DB2" value="db2"/>               <property name="Oracle" value="oracle" />               <property name="MySql" value="mysql" />            </databaseIdProvider>      *  解析到:org.apache.ibatis.session.Configuration#databaseId      */     databaseIdProviderElement(root.evalNode("databaseIdProvider"));     /**      * 解析我们的类型处理器节点      * <typeHandlers>           <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>         </typeHandlers>         解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap      */     typeHandlerElement(root.evalNode("typeHandlers"));     /**      * 最最最最最重要的就是解析我们的mapper      *      resource:来注册我们的class类路径下的      url:来指定我们磁盘下的或者网络资源的      class:      若注册Mapper不带xml文件的,这里可以直接注册      若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径      -->      <mappers>         <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>         <mapper class="com.xxx.mapper.DeptMapper"></mapper>           <package name="com.xxx.mapper"></package>         -->      </mappers>      * package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers                2.      */     mapperElement(root.evalNode("mappers"));   } catch (Exception e) {     throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);   } } 复制代码

mapperElement方法

private void mapperElement(XNode parent) throws Exception {   if (parent != null) {     /**      * 获取我们mappers节点下的一个一个的mapper节点      */     for (XNode child : parent.getChildren()) {       /**        * 判断我们mapper是不是通过批量注册的        * <package name="com.xxx.mapper"></package>        */       if ("package".equals(child.getName())) {         String mapperPackage = child.getStringAttribute("name");         configuration.addMappers(mapperPackage);       } else {         /**          * 判断从classpath下读取我们的mapper          * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>          */         String resource = child.getStringAttribute("resource");         /**          * 判断是不是从我们的网络资源读取(或者本地磁盘得)          * <mapper url="D:/mapper/EmployeeMapper.xml"/>          */         String url = child.getStringAttribute("url");         /**          * 解析这种类型(要求接口和xml在同一个包下)          * <mapper class="com.xxx.mapper.DeptMapper"></mapper>          *          */         String mapperClass = child.getStringAttribute("class");         /**          * 我们得mappers节点只配置了          * <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>          */         if (resource != null && url == null && mapperClass == null) {           ErrorContext.instance().resource(resource);           /**            * 把我们的文件读取出一个流            */           InputStream inputStream = Resources.getResourceAsStream(resource);           /**            * 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件            */           XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());           /**            * 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)            */           mapperParser.parse();         } else if (resource == null && url != null && mapperClass == null) {           ErrorContext.instance().resource(url);           InputStream inputStream = Resources.getUrlAsStream(url);           XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());           mapperParser.parse();         } else if (resource == null && url == null && mapperClass != null) {           Class<?> mapperInterface = Resources.classForName(mapperClass);           configuration.addMapper(mapperInterface);         } else {           throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");         }       }     }   } } 复制代码

addMapper方法

/**  * 方法实现说明:把我们的Mapper class保存到我们的knownMappers map 中  * @param type:我们的Mapper接口  * @return:  * @exception:  * @date:2019/8/22 20:29  */ public <T> void addMapper(Class<T> type) {   /**    * 判断我们传入进来的type类型是不是接口    */   if (type.isInterface()) {     /**      * 判断我们的缓存中有没有该类型      */     if (hasMapper(type)) {       throw new BindingException("Type " + type + " is already known to the MapperRegistry.");     }     boolean loadCompleted = false;     try {       /**        * 创建一个MapperProxyFactory 把我们的Mapper接口保存到工厂类中        */       knownMappers.put(type, new MapperProxyFactory<>(type));       // It's important that the type is added before the parser is run       // otherwise the binding may automatically be attempted by the       // mapper parser. If the type is already known, it won't try.    mapper注解构造器       MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);       /**        * 进行解析        */       parser.parse();       loadCompleted = true;     } finally {       if (!loadCompleted) {         knownMappers.remove(type);       }     }   } } 复制代码

MapperAnnotationBuilder.parser方法

public void parse() {   String resource = type.toString();   // 是否已经解析mapper接口对应的xml   if (!configuration.isResourceLoaded(resource)) {     // 根据mapper接口名获取 xml文件并解析,  解析<mapper></mapper>里面所有东西放到configuration     loadXmlResource();     // 添加已解析的标记     configuration.addLoadedResource(resource);     assistant.setCurrentNamespace(type.getName());     parseCache();     parseCacheRef();     // 获取所有方法 看是不是用了注解     Method[] methods = type.getMethods();     for (Method method : methods) {       try {         // issue #237         if (!method.isBridge()) {           // 是不是用了注解  用了注解会将注解解析成MappedStatement           parseStatement(method);         }       } catch (IncompleteElementException e) {         configuration.addIncompleteMethod(new MethodResolver(this, method));       }     }   }   parsePendingMethods(); } 复制代码

loadXmlResource方法

private void loadXmlResource() {   // Spring may not know the real resource name so we check a flag   // to prevent loading again a resource twice   // this flag is set at XMLMapperBuilder#bindMapperForNamespace   if (!configuration.isResourceLoaded("namespace:" + type.getName())) {     String xmlResource = type.getName().replace('.', '/') + ".xml";     // #1347     InputStream inputStream = type.getResourceAsStream("/" + xmlResource);     if (inputStream == null) {       // Search XML mapper that is not in the module but in the classpath.       try {         inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);       } catch (IOException e2) {         // ignore, resource is not required       }     }     if (inputStream != null) {       XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());       xmlParser.parse();     }   } } 复制代码

XMLMapperBuilder.parse方法

/**  * 方法实现说明:真正的去解析我们的Mapper.xml(EmployeeMapper.xml)  *   * @return:  * @exception:  * @date:2019/8/30 16:43  */ public void parse() {   /**    * 判断当前的Mapper是否被加载过    */   if (!configuration.isResourceLoaded(resource)) {     /**      * 真正的解析我们的 <mapper namespace="com.xxx.mapper.EmployeeMapper">      *      */     configurationElement(parser.evalNode("/mapper"));     /**      * 把资源保存到我们Configuration中      */     configuration.addLoadedResource(resource);     bindMapperForNamespace();   }   parsePendingResultMaps();   parsePendingCacheRefs();   parsePendingStatements(); } 复制代码

configurationElement方法

/**  * 方法实现说明:解析我们的<mapper></mapper>节点  * @author:xsls  * @param context document节点  * @return:  * @exception:  * @date:2019/8/31 13:34  */ private void configurationElement(XNode context) {   try {     /**      * 解析我们的namespace属性      * <mapper namespace="com.xxx.mapper.EmployeeMapper">      */     String namespace = context.getStringAttribute("namespace");     if (namespace == null || namespace.equals("")) {       throw new BuilderException("Mapper's namespace cannot be empty");     }     /**      * 保存我们当前的namespace  并且判断接口完全类名==namespace      */     builderAssistant.setCurrentNamespace(namespace);     /**      * 解析我们的缓存引用      * 说明我当前的缓存引用和DeptMapper的缓存引用一致      * <cache-ref namespace="com.xxx.mapper.DeptMapper"></cache-ref>           解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>           异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs      */     cacheRefElement(context.evalNode("cache-ref"));     /**      * 解析我们的cache节点      * <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>         解析到:org.apache.ibatis.session.Configuration#caches                org.apache.ibatis.builder.MapperBuilderAssistant#currentCache      */     cacheElement(context.evalNode("cache"));     /**      * 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)      */     parameterMapElement(context.evalNodes("/mapper/parameterMap"));     /**      * 解析我们的resultMap节点      * 解析到:org.apache.ibatis.session.Configuration#resultMaps      *    异常 org.apache.ibatis.session.Configuration#incompleteResultMaps      *      */     resultMapElements(context.evalNodes("/mapper/resultMap"));     /**      * 解析我们通过sql节点      *  解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments      *   其实等于 org.apache.ibatis.session.Configuration#sqlFragments      *   因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了      */     sqlElement(context.evalNodes("/mapper/sql"));     /**      * 解析我们的select | insert |update |delete节点      * 解析到org.apache.ibatis.session.Configuration#mappedStatements      */     buildStatementFromContext(context.evalNodes("select|insert|update|delete"));   } catch (Exception e) {     throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);   } } 复制代码

cacheElement方法

/**  * 方法实现说明:解析缓存属性  *    <cache type=""       eviction="FIFO"       flushInterval="60000"       size="512"       readOnly="true"/>  *   * @param context:cache节点  * @return:  * @exception:  * @date:2019/8/31 14:13  */ private void cacheElement(XNode context) {   if (context != null) {     //解析cache节点的type属性     String type = context.getStringAttribute("type", "PERPETUAL");     //根据type的String获取class类型     Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);     //获取缓存过期策略:默认是LRU     String eviction = context.getStringAttribute("eviction", "LRU");     Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);     //flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。     Long flushInterval = context.getLongAttribute("flushInterval");     //size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。     Integer size = context.getIntAttribute("size");     //只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false     boolean readWrite = !context.getBooleanAttribute("readOnly", false);     boolean blocking = context.getBooleanAttribute("blocking", false);     Properties props = context.getChildrenAsProperties();     //把缓存节点加入到Configuration中     builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);   } } 复制代码

useNewCache方法

public Cache useNewCache(Class<? extends Cache> typeClass,     Class<? extends Cache> evictionClass,     Long flushInterval,     Integer size,     boolean readWrite,     boolean blocking,     Properties props) {   Cache cache = new CacheBuilder(currentNamespace)       .implementation(valueOrDefault(typeClass, PerpetualCache.class))       .addDecorator(valueOrDefault(evictionClass, LruCache.class))       .clearInterval(flushInterval)       .size(size)       .readWrite(readWrite)       .blocking(blocking)       .properties(props)       .build();   configuration.addCache(cache);   currentCache = cache;   return cache; } 复制代码

CacheBuilder.build方法

public Cache build() {   setDefaultImplementations();   Cache cache = newBaseCacheInstance(implementation, id);   setCacheProperties(cache);   // issue #352, do not apply decorators to custom caches   if (PerpetualCache.class.equals(cache.getClass())) {     for (Class<? extends Cache> decorator : decorators) {       cache = newCacheDecoratorInstance(decorator, cache);       setCacheProperties(cache);     }     cache = setStandardDecorators(cache);   } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {     cache = new LoggingCache(cache);   }   return cache; } 复制代码

setStandardDecorators方法

private Cache setStandardDecorators(Cache cache) {   try {     MetaObject metaCache = SystemMetaObject.forObject(cache);     if (size != null && metaCache.hasSetter("size")) {       metaCache.setValue("size", size);     }     if (clearInterval != null) {       cache = new ScheduledCache(cache);//ScheduledCache:调度缓存,负责定时清空缓存       ((ScheduledCache) cache).setClearInterval(clearInterval);     }     if (readWrite) {  // 将LRU 装饰到Serialized       cache = new SerializedCache(cache); //SerializedCache:缓存序列化和反序列化存储     }     cache = new LoggingCache(cache);     cache = new SynchronizedCache(cache);     if (blocking) {       cache = new BlockingCache(cache);     }     return cache;   } catch (Exception e) {     throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);   } } 复制代码

这边使用装饰者模式对cache进行增强。

buildStatementFromContext方法

/**  * 方法实现说明:解析我们得得select|update|delte|insert节点然后  * 创建我们得mapperStatment对象  *   * @param list:所有的select|update|delte|insert节点  * @param requiredDatabaseId:判断有没有数据库厂商Id  * @return:  * @exception:  * @date:2019/9/5 21:35  */ private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {   /**    * 循环我们的select|delte|insert|update节点    */   for (XNode context : list) {     /**      * 创建一个xmlStatement的构建器对象      */     final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);     try {       statementParser.parseStatementNode();     } catch (IncompleteElementException e) {       configuration.addIncompleteStatement(statementParser);     }   } } 复制代码

XMLStatementBuilder.parseStatementNode方法

public void parseStatementNode() {   /**    * 我们的insert|delte|update|select 语句的sqlId    */   String id = context.getStringAttribute("id");   /**    * 判断我们的insert|delte|update|select  节点是否配置了    * 数据库厂商标注    */   String databaseId = context.getStringAttribute("databaseId");   /**    * 匹配当前的数据库厂商id是否匹配当前数据源的厂商id    */   if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {     return;   }   /**    * 获得节点名称:select|insert|update|delete    */   String nodeName = context.getNode().getNodeName();   /**    * 根据nodeName 获得 SqlCommandType枚举    */   SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));   /**    * 判断是不是select语句节点    */   boolean isSelect = sqlCommandType == SqlCommandType.SELECT;   /**    *  获取flushCache属性    *  默认值为isSelect的反值:查询:默认flushCache=false   增删改:默认flushCache=true    */   boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);   /**    * 获取useCache属性    * 默认值为isSelect:查询:默认useCache=true   增删改:默认useCache=false    */   boolean useCache = context.getBooleanAttribute("useCache", isSelect);   /**    * resultOrdered:  是否需要处理嵌套查询结果 group by (使用极少)    * 可以将比如 30条数据的三组数据  组成一个嵌套的查询结果    */   boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);   /**    * 解析我们的sql公用片段    *     <select id="qryEmployeeById" resultType="Employee" parameterType="int">             <include refid="selectInfo"></include>             employee where id=#{id}         </select>       将 <include refid="selectInfo"></include> 解析成sql语句 放在<select>Node的子节点中    */   // Include Fragments before parsing   XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);   includeParser.applyIncludes(context.getNode());   /**    * 解析我们sql节点的参数类型    */   String parameterType = context.getStringAttribute("parameterType");   // 把参数类型字符串转化为class   Class<?> parameterTypeClass = resolveClass(parameterType);   /**    * 查看sql是否支撑自定义语言    * <delete id="delEmployeeById" parameterType="int">    <settings>         <setting name="defaultScriptingLanguage" value="xxxLang"/>    </settings>    */   String.getStringAttribute("lang");   /**    * 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver    */   LanguageDriver langDriver = getLanguageDriver(lang);   // Parse selectKey after includes and remove them.   /**    * 解析我们<insert 语句的的selectKey节点, 还记得吧,一般在oracle里面设置自增id    */   processSelectKeyNodes(id, parameterTypeClass, langDriver);   // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)   /**    * 我们insert语句 用于主键生成组件    */   KeyGenerator keyGenerator;   /**    * selectById!selectKey    * id+!selectKey    */   String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;   /**    * 把我们的命名空间拼接到keyStatementId中    * com.xxx.mapper.Employee.saveEmployee!selectKey    */   keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);   /**    *<insert id="saveEmployee" parameterType="com.xxx.entity.Employee" useGeneratedKeys="true" keyProperty="id">    *判断我们全局的配置类configuration中是否包含以及解析过的组件生成器对象    */   if (configuration.hasKeyGenerator(keyStatementId)) {     keyGenerator = configuration.getKeyGenerator(keyStatementId);   } else {     /**      * 若我们配置了useGeneratedKeys 那么就去除useGeneratedKeys的配置值,      * 否者就看我们的mybatis-config.xml配置文件中是配置了      * <setting name="useGeneratedKeys" value="true"></setting> 默认是false      * 并且判断sql操作类型是否为insert      * 若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE      * 否则就是NoKeyGenerator.INSTANCE      */     keyGenerator = context.getBooleanAttribute("useGeneratedKeys",         configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))         ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;   }   /**    * 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的    * sql脚本对象  .  解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析    */   SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);   /**    * STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED    */   StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));   /**    * 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)    */   Integer fetchSize = context.getIntAttribute("fetchSize");   /**    * 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。    */   Integer timeout = context.getIntAttribute("timeout");   /**    * 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置    */   String parameterMap = context.getStringAttribute("parameterMap");   /**    * 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。    * 可以使用 resultType 或 resultMap,但不能同时使用    */   String resultType = context.getStringAttribute("resultType");   /**解析我们查询结果集返回的类型     */   Class<?> resultTypeClass = resolveClass(resultType);   /**    * 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。    * 可以使用 resultMap 或 resultType,但不能同时使用。    */   String resultMap = context.getStringAttribute("resultMap");   String resultSetType = context.getStringAttribute("resultSetType");   ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);   if (resultSetTypeEnum == null) {     resultSetTypeEnum = configuration.getDefaultResultSetType();   }   /**    * 解析 keyProperty  keyColumn 仅适用于 insert 和 update    */   String keyProperty = context.getStringAttribute("keyProperty");   String keyColumn = context.getStringAttribute("keyColumn");   String resultSets = context.getStringAttribute("resultSets");   /**    * 为我们的insert|delete|update|select节点构建成我们的mappedStatment对象    */   builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,       fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,       resultSetTypeEnum, flushCache, useCache, resultOrdered,       keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }


作者:没伞的孩子
链接:https://juejin.cn/post/7035466060782370829

 伪原创工具 SEO网站优化  https://www.237it.com/


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐