Mybatis源码分析(mybatis源码解析)
MyBatis介绍
MyBatis是一个持久层的ORM框架,使用简单,学习成本较低。可以执行自己手写的SQL语句,比较灵活。但是MyBatis的自动化程度不高,移植性也不高,有时从一个数据库迁移到另外一个数据库的时候需要自己修改配置,所以称只为半自动ORM框架。
Mybaits整体体系图
例子
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. } } } 复制代码
可以看到除了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/