MyBatis is introduced
MyBatis is a persistence layer ORM framework that is easy to use and low cost to learn. Can execute their own handwritten SQL statements, more flexible. However, MyBatis does not have a high degree of automation and portability. Sometimes it needs to modify the configuration when migrating from one database to another database, so it is called semi-automatic ORM framework.
Mybaits overall system diagram
example
public class App { public static void main(String[] args) { String resource = "mybatis-config.xml"; Reader reader; Try {/ / to build the XML Configuration files into the Configuration Configuration class reader = Resources. GetResourceAsReader (resource); // Build a SqlSessionFactory by loading the configuration file stream DefaultSqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); // Data source executor DefaultSqlSession SqlSession Session = SQLmapper.openSession (); Try {/ / perform query the underlying 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(); }}}Copy the code
build
methods
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. } } }Copy the code
You can see that in addition toXMLConfigBuilder
Inheritance inBaseBuilder
There are other classes that parse configuration files.
XMLConfigBuilder.parse
methods
Public Configuration Parse () {/** * throws an exception if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once."); } /** * parsed = true; /** * Parse our mybatis-config. XML * node * <configuration> ** * </configuration> */ parseConfiguration(parser.evalNode("/configuration")); return configuration; }Copy the code
parseConfiguration
methods
Private void parseConfiguration(XNode root) {try {/** * / > * resolves to org. Apache. Ibatis. Parsing. XPathParser# variables * org in apache. Ibatis. Session. Configuration# variables * / propertiesElement(root.evalNode("properties")); / * * * parsing our mybatis - config. What you can configure the Settings of XML node * attributes: 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 indicates a virtual file system. Mainly through the program can easily read the local file system, FTP file system and other system file resources. Mybatis provides the VFS configuration to load custom virtual file system applications to: org.apache.ibatis.session.Configuration#vfsImpl */ loadCustomVfs(settings); /** * Specify a specific implementation of the log used by MyBatis. If not specified, it will be automatically found. * SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING * Resolve to 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 > resolution to oorg. The apache. Ibatis. Session. Configuration# typeAliasRegistry. TypeAliases */ typeAliasesElement(root.evalNode("typeAliases")); * execute (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")); // Set the Settings and default settingsElement(Settings); // Read it after objectFactory and objectWrapperFactory issue #631 /** * <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> </environment> Org. Apache. Ibatis. Session. Configuration# environment * in the case of integrated spring is provided by spring - mybatis data sources and * / transaction factory environmentsElement(root.evalNode("environments")); /** * resolve the database vendor * <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> * resolves to: org.apache.ibatis.session.Configuration#databaseId */ databaseIdProviderElement(root.evalNode("databaseIdProvider")); / parsing we * * * * the type of processor nodes < typeHandlers > < typeHandler handler = "org. Mybatis. Example. ExampleTypeHandler" / > < / typeHandlers > resolution to: org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap */ typeHandlerElement(root.evalNode("typeHandlers")); /** * The most important thing is to parse our mapper * resource: to register the url in our class classpath: to specify the class of our disk or network resource: If you want to register a Mapper with an XML file, Need to get the XML files and mapper file path with the same name - > < mappers > < mapper resource = "mybatis mapper/EmployeeMapper. XML" / > < mapper class="com.xxx.mapper.DeptMapper"></mapper> <package name="com.xxx.mapper"></package> --> </mappers> * package 1. The mapper interface resolves to: 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); }}Copy the code
mapperElement
methods
private void mapperElement(XNode parent) throws Exception { if (parent ! /** * get mapper nodes under mappers */ for (XNode child: Parent.getchildren ()) {/** * check whether mapper is registered by batch * <package name="com.xxx.mapper"></package> */ if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else {/ * * * judge read our mapper from under the classpath * < mapper resource = "mybatis mapper/EmployeeMapper. XML" / > * / String resource = child.getStringAttribute("resource"); / * * * determine whether from our network resources to read (or local disk) * < mapper url = "D: / mapper/EmployeeMapper. XML" / > * / String url = child.getStringAttribute("url"); / * * * resolution of this type (interface and XML in the same package) * < mapper class = "com. XXX. Mapper. DeptMapper" > < / mapper > * * / String mapperClass = child.getStringAttribute("class"); / * * * we have mappers node configuration only * < mapper resource = "mybatis mapper/EmployeeMapper. XML" / > * / if (the resource! = null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); / * * * * remove the file we read a flow/InputStream InputStream = Resources. The getResourceAsStream (resource); / / XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, XMLMapperBuilder) configuration, resource, configuration.getSqlFragments()); Mapperparser.parse (); /* parseparser.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."); } } } } }Copy the code
addMapper
methods
/** * Save our Mapper class to our knownMappers map * @param type: our Mapper interface * @return: * @exception: * @date:2019/8/22 20:29 */ public <T> void addMapper(Class<T> type) {/** * (type.isInterface()) {if (hasMapper(type)) {throw new BindingException(" type "+ type +" is already known to the MapperRegistry."); } boolean loadCompleted = false; */ 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, MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); Parse (); / / parser.parse(); loadCompleted = true; } finally { if (! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
MapperAnnotationBuilder.parser
methods
public void parse() { String resource = type.toString(); // Whether mapper interface corresponding XML if (! Configuration. IsResourceLoaded (resource)) {/ / according to the mapper interface name get and parse the XML file, Parse everything in <mapper></mapper> into configuration loadXmlResource(); / / add parsed tag configuration. AddLoadedResource (resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); [] 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(); }Copy the code
loadXmlResource
methods
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();
}
}
}
Copy the code
XMLMapperBuilder.parse
methods
Mapper. XML (employeemapper.xml) ** @return: * @exception: * @date:2019/8/30 16:43 */ public void parse() {/** * If (! Configuration. IsResourceLoaded (resource)) {/ * * * real parsing our < mapper namespace = "com. XXX. Mapper. EmployeeMapper" > * * / configurationElement(parser.evalNode("/mapper")); / * * * save resources to our Configuration in the * / Configuration in addLoadedResource (resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }Copy the code
configurationElement
methods
XSLS: <mapper></mapper> </ author: XSLS * @param context document * @return: * @exception: * @date:2019/8/31 13:34 */ private void configurationElement(XNode context) {try {/** * Parse our namespace property * <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"); } / * * * to save our current namespace and judge interface class name = = completely namespace. * / builderAssistant setCurrentNamespace (namespace). / resolve cache references we * * * * shows my current cache references and DeptMapper cache reference * < cache - ref namespace = "com. XXX. Mapper. DeptMapper" > < / cache - ref > Resolve to org. Apache. Ibatis. Session. Configuration# cacheRefMap < the current namespace, ref - namespace > under the abnormal (reference cache unused cache) : org.apache.ibatis.session.Configuration#incompleteCacheRefs */ cacheRefElement(context.evalNode("cache-ref")); / analytical our cache node * * * * < cache type = "org. Mybatis. Caches. Ehcache. EhcacheCache" > < / cache > resolution to: org.apache.ibatis.session.Configuration#caches org.apache.ibatis.builder.MapperBuilderAssistant#currentCache */ cacheElement(context.evalNode("cache")); /** * Parsing the paramterMap node */ parameterMapElement(context.evalNodes("/mapper/parameterMap")); /** * Parses our resultMap node * to: Org. Apache. Ibatis. Session. Configuration# resultMaps * abnormal org in apache. The ibatis. Session. Configuration# incompleteResultMaps * */ resultMapElements(context.evalNodes("/mapper/resultMap")); / * * * * we through SQL node parse to org. The apache. Ibatis. Builder. XML. XMLMapperBuilder# sqlFragments * is equal to zero Org. Apache. Ibatis. Session. Configuration# sqlFragments * because they are the same reference, While concocting XMLMapperBuilder Configuration. GetSqlFragments preach in * / sqlElement (context. EvalNodes ("/mapper/SQL ")); / * * * parsing we select | insert | update | delete node * resolution to 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); }}Copy the code
cacheElement
methods
* <cache type="" evo "flushInterval="60000" size="512" readOnly="true"/> ** @param Context :cache node * @return: * @exception: * @date:2019/8/31 14:13 */ private void cacheElement(XNode context) { if (context ! = null) {/ / parse the cache node type attribute String type = context. GetStringAttribute (" type ", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); / / for cache expiration policies: default is LRU String eviction. = the context getStringAttribute (" eviction ", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); The flushInterval property can be set to any positive integer, and the value should be a reasonable amount of time in milliseconds. The default is no, that is, there is no refresh interval, and the cache is flushed only when the statement is called. Long flushInterval = context.getLongAttribute("flushInterval"); The size (number of references) attribute can be set to any positive integer, taking into account the size of the object to cache and the memory resources available in the runtime environment. The default value is 1024. Integer size = context.getIntAttribute("size"); // Read-only) property can be set to true or false. A read-only cache returns the same instance of the cache object to all callers. Therefore, these objects cannot be modified. This provides a significant performance boost. A read-write cache returns (through serialization) a copy of the cached object. This is slower, but more secure, so the default is false Boolean readWrite =! context.getBooleanAttribute("readOnly", false); boolean blocking = context.getBooleanAttribute("blocking", false); Properties props = context.getChildrenAsProperties(); / / the cache node join the Configuration builderAssistant. UseNewCache (typeClass evictionClass, flushInterval, size, readWrite, blocking, props); }}Copy the code
useNewCache
methods
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;
}
Copy the code
CacheBuilder.build
methods
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; }Copy the code
setStandardDecorators
methods
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: scheduling cache, which is responsible for periodically clearing the cache ((ScheduledCache) cache). } if (readWrite) {// Set LRU to Serialized cache = new SerializedCache(cache); //SerializedCache: cache serialization and deserialization store} 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); }}Copy the code
The cache is enhanced with decorator mode.
buildStatementFromContext
methods
/ * * * method implementation notes: parsing we have to have to select | update | delte | insert node and then * * * objects created we need mapperStatment @ param List: all select | update | delte | insert node * @ param requiredDatabaseId: judge any database vendor Id * @ return: * @ exception: * @date:2019/9/5 21:35 */ private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {/ * * * cycle we select | delte | insert | * / update node for (XNode context: List) {/** * Create a builder object for xmlStatement */ final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code
XMLStatementBuilder.parseStatementNode
methods
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" lang="xxxLang">
<settings>
<setting name="defaultScriptingLanguage" value="xxxLang"/>
</settings>
*/
String lang = context.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);
}
Copy the code
The above is the whole process of mybatis parsing configuration file source process. Then openSession opens the database session. Then the source code analysis of the database operation process.
openSession
methods
There are two implementations, entryDefaultSqlSessionFactory
Class.
openSessionFromDataSource
methods
* @param execType: executor type * @param level: isolation level * @return:SqlSession * @exception: * @date:2019/9/9 13:38 */ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; Try {/ * * * access to Environment variables * / final Environment Environment. = the configuration getEnvironment (); / * * * * to transaction factory/final TransactionFactory TransactionFactory = getTransactionFactoryFromEnvironment (environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); CacheExecutor (SimpleExecutor); /** * Create an SQL executor object; /** * Create an SQL executor object; /** * Normally we return a cacheExecutor if our mybaits global configuration file's cacheEnabled is true by default Detailed look at the following newExecutor method * / final Executor Executor = configuration. NewExecutor (tx, execType); DeaultSqlSessoin */ return new DefaultSqlSession(Configuration, Executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}Copy the code
newExecutor
methods
/** * create a SQL statement execution object * @param Transaction: transaction * @param executorType: execution type * @return:Executor execution object * @exception: * @date:2019/9/9 13:59 */ public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; BATCH == ExecutorType (ExecutorType) {executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == ExecutorType) {// New ReuseExecutor(this, transaction); } else {// Simple SQL executor object executor = new SimpleExecutor(this, transaction); If (cacheEnabled) {// Wrap the current simple executor as a CachingExecutor executor = new CachingExecutor(executor); } / * * * TODO: call all the interceptors * / executor = plugin object method (executor) interceptorChain. PluginAll (executor); return executor; }Copy the code
selectOne
methods
/ * * * method implementation notes: query we when an object * @ param statement: our statementId (com) XXX. Mapper. EmployeeMapper. FindOne) * @ param * @return: T return result * @exception: * @date:2019/9/9 20:26 */ @Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. /** * */ List<T> List = this.selectList(statement, parameter); Return list.get(0); if (list.size() == 1) {return list.get(0); } else if (list.size() > 1) {Expected one result (or null) to be returned by selectOne(), Expected one result (or null) to be returned by selectOne(), but found: " + list.size() */ throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}Copy the code
selectList
methods
@param Statement: statementId * @param parameter: parameter object * @param rowBounds: logical paging object of mybiats * @return: * @exception: * @date:2019/9/9 20:33 */ @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds RowBounds) {try {/** * Through our statement to our global configuration class gets MappedStatement * / MappedStatement ms = configuration. GetMappedStatement (statement); /** * Execute our SQL object through the executor * step 1: wrap our collection class parameters * Step 2: Normally executor is a cacheExetory object */ return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}Copy the code
wrapCollection
methods
/** * wrap our class parameters * @param object: parameter object * @return: wrapped object * @exception: * @date:2019/9/9 20:36 */ private Object wrapCollection(final Object Object) {// If our parameter type is Collection if (Object) instanceof Collection) { StrictMap<Object> map = new StrictMap<>(); Map. put("collection", object); If (object instanceof list) {map.put("list", object); if (object instanceof list) {map.put("list", object); } return map; } else if (object ! = null && object.getClass().isarray ()) {// If array StrictMap< object > map = new StrictMap<>(); map.put("array", object); return map; } return object; }Copy the code
CachingExecutor.query
methods
/ * * * method implementation notes: through our SQL execution object, execute SQL * @ param ms encapsulates our each insert | delete | update | select object * @ param parameterObject: * parameter object @param rowBounds: Mybaits logical paging object TODO????? * @param resultHandler: resultHandler object * @return: * @exception: */ @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { /** * Through analytical our SQL parameter object details, 1339025938:1570540512: com. XXX. Mapper. SelectById: 0:21 47483647: select id, user_name, create_time the from t_user where id=? :1:development */ BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }Copy the code
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, <cache></cache> */ cache = ms.getCache(); /** * If (cache! = null) {flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); List<E> List = (List<E>) Tcm.getobject (cache, key); / / @suppressWarnings ("unchecked") List<E> List = (List<E>) TCM.getobject (cache, key); List = delegate.query(ms, parameterObject, rowBounds, parameterObject) {// Query list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); Tcm. putObject(cache, key, list); // Add tcm.putObject(cache, key, list); // issue #578 and #116 } return list; Return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }Copy the code
BaseExecutor.query
methods
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); If (closed) {throw new ExecutorException("Executor was closed "); } // <2> Clear the local cache if queryStack is zero and the local cache is required to clear. if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; Try {// <4.1> get query results from level 1 cache queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; If (list! = null) {/ / processing saved handleLocallyCachedOutputParameters (ms, the key parameter, boundSql); } else {// do not get, List = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }Copy the code
queryFromDatabase
methods
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); List = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }Copy the code
SimpleExecutor.doQuery
methods
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); / / detail see below newStatementHandler method StatementHandler handler = configuration. NewStatementHandler method (wrapper, ms, parameter, rowBounds, resultHandler, boundSql); STMT = prepareStatement(handler, Ms. GetStatementLog ()); PreparedStatementHandler return handler.query(STMT, resultHandler); } finally { closeStatement(stmt); }}Copy the code
newStatementHandler
methods
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
Copy the code
prepareStatement
methods
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
Copy the code
PreparedStatementHandler.query
methods
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // Execute the database operation ps.execute(); return resultSetHandler.handleResultSets(ps); }Copy the code