mybatis的解析和运行原理1
来源:互联网 发布:php图书管理系统 编辑:程序博客网 时间:2024/05/20 05:54
如果只是学会mybatis的使用,那么在之前的博客中对mybatis一些基本的使用已经做了比较详细的说明了。但是在开发中,对于很多东西我们需要知道原理,才能对源码进行修改,写出更好的代码,对mybatis理解的更加深入,做到知其然并知其所以然。之前的博客:http://blog.csdn.net/j903829182/article/details/73382280
接下来是对mybatis的底层设计和实现原理做一些研究。mybatis的运行分为两部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。相对而言,SqlSessionFactory的创建比较容易理解,而SqlSession的执行过程就不是那么简单了,它将包括许多辅助的技术,我们先讨论反射技术和动态代理技术,这是揭示mybatis底层架构的基础。
当我们掌握了mybatis的运行原理以后,我们就可以知道mybatis是怎么运行的,这是后面学习插件技术的基础。
一,涉及的技术难点简介
我们知道Mapper仅仅是一个接口,而不是包含逻辑的实现类,一个接口是没办法执行的,那么它是怎么运行的呢?其实很显然Mapper产生了代理类,这个代理类是Mybatis为我们创建的,为此先学习下代理。
首先,代理有一个代理模式。所谓的代理模式就是在原有的服务上多加一个占位,通过这个占位去控制服务的访问。一般而言,动态代理分为两种,一种是基于JDK反射机制提供的代理,另外一种是CGLIB代理。在JKD提供的代理,我们必须要提供接口,而CGLIB则不需要提供接口,在mybatis里面两种动态技术都已经使用了。在学习之前先学习下反射技术。
1,反射技术
在java中反射在很多地方都有用到,下面来实现一个简单反射的例子。
package com.jack.reflect;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * create by jack 2017/12/18 */public class ReflectService { /** * 服务方法 * @param name */ public void sayHello(String name){ System.out.println("hello , "+name); } public static void main(String[] args) { try { //通过反射创建ReflectService对象 Object service = Class.forName(ReflectService.class.getName()).newInstance(); //获取服务方法-sayHello Method method = service.getClass().getMethod("sayHello",String.class); //反射调用方法 method.invoke(service, "jack"); System.out.println(service.getClass()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }}
上面的代码通过反射技术去创建ReflectService对象,获取方法后通过反射调用。
反射调用的最大好处是配置性大大提高了,就如同spring ioc容器一样,我们可以给很多配置设置参数,使得java应用程序能够顺利运行起来,大大提高了java的灵活性和可配置性,降低了模块之间的耦合。
2,JDK动态代理
JDK的动态代理,是由JDK的java.lang.reflect.*包提供支持的,我们需要完成下面两步:
1)编写服务的类和接口,这个是真正的服务提供者,在JDK代理中接口是必须的。
2)编写代理类,提供邦定和代理方法
JDK的代理最大的缺点是需要提供接口,而mybatis的Mapper就是一个接口,它采用的就是JDK的动态代理。下面先给出一个服务接口:
package com.jack.service;/** * create by jack 2017/12/18 */public interface HelloService { public void sayHello(String name);}
然后是一个实现类,代码如下:
package com.jack.impl;import com.jack.service.HelloService;/** * create by jack 2017/12/18 */public class HelloServiceImpl implements HelloService{ public void sayHello(String name) { System.out.println("hello , "+name); }}
下面我们编写一个代理类,提供真实对象的绑定和代理方法。代理类的要求是实现InvocationHandler接口的代理方法,当一个对象被绑定后,执行其方法的时候就会进入到代理方法里,代码如下:
package com.jack.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * create by jack 2017/12/18 */public class HelloServiceProxy implements InvocationHandler{ /** * 真实服务对象 */ private Object target; /** *通过代理对象调用方法首先进入这个方法 * @param proxy 代理对象 * @param method 被调用方法 * @param args 方法的参数 * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-----------这是JDK动态代理---------------"); Object result = null; //反射方法前调用 System.out.println("我准备说hello......"); //执行方法,相当于调用HelloServiceImpl类的sayHello方法 result = method.invoke(target, args); //反射方法后调用 System.out.println("我说过hello了"); return result; } /** *绑定一个委托对象并返回一个代理类 * @param target * @return */ public Object bind(Object target) { this.target = target; //取得代理对象,jdk代理需要提供接口 return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this); }}
//取得代理对象,jdk代理需要提供接口
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
上面这段代码让JDK产生了一个代理对象。这个代理对象有三个参数:第一个参数是类加载器,第二个参数是接口(代理对象挂在哪个接口下),第三个参数是this代表当前HelloServiceProxy类,换句话说是使用HelloServiceProxy的代理方法作为对象的代理执行者。
一旦绑定后,在进入代理对象方法调用的时候就会到HelloServiceProxy的代理方法上,代理方法有三个参数:第一个proxy是代理对象,第二是当前调用的那个方法,第三个是方法的参数。比如说,现在HelloServiceImpl对象(obj)用bind方法绑定后,返回其占位,我们再调用proxy.sayHello()方法,那么它就会进入到HelloServiceProxy的invoke()方法。而invoke参数中第一个便是代理对象proxy,方法便是sayHello。
我们已经用HelloServiceProxy类的属性target保存了真实的服务对象,那么我们可以通过反射技术调度真实对象的方法。
result = method.invoke(target,args);
这里我们演示了JDK动态代理的实现,并且在调用方法前后都可以加入我们想要的东西。mybatis在使用Mapper的时候也是这样做的。
下面我们测试下动态代理,代码如下:
package com.jack.test;import com.jack.impl.HelloServiceImpl;import com.jack.proxy.HelloServiceProxy;import com.jack.service.HelloService;/** * create by jack 2017/12/18 */public class HelloServiceMain { public static void main(String[] args) { //创建实现代理接口对象 HelloServiceProxy HelloHandler = new HelloServiceProxy(); //获取代理对象 HelloService proxy = (HelloService) HelloHandler.bind(new HelloServiceImpl()); //方法调用 proxy.sayHello("jack"); }}
运行测试代码,输出如下:
-----------这是JDK动态代理---------------我准备说hello......hello , jack我说过hello了Process finished with exit code 0
3,CGLIB动态代理
JDK提供的动态代理存在一个缺陷,就是你必须提供接口才可以使用,为了克服这个缺陷,我们可以使用开源框架CGLIB,它是一种流行的动态代理。
下面我们看看如何使用CGLIB动态代理。HelloService和HelloServiceImpl类都不需要改变,但是我们要实现CGLIB的代理类。要实现CGLIB实现代理,首先需要引入cglib包,引入代码如下:
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
它的实现MethodInterceptor的代理方法如下:
package com.jack.impl;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/** * create by jack 2017/12/18 */public class HelloServiceCgLib implements MethodInterceptor { private Object target; /** * 创建代理对象 * @param target * @return */ public Object getInstance(Object target){ this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); //回调方法 enhancer.setCallback(this); //创建代理对象 return enhancer.create(); } /** * 回调方法 * @param object * @param method * @param objects * @param methodProxy * @return * @throws Throwable */ public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("------------我是CGLIB的动态代理--------------"); //反射方法前调用 System.out.println("我准备说hello"); Object returnObj = methodProxy.invokeSuper(object, objects); //反射方法后调用 System.out.println("我说过hello了"); return returnObj; }}
测试cglib动态代理的代码如下:
package com.jack.test;import com.jack.impl.HelloServiceCgLib;import com.jack.impl.HelloServiceImpl;/** * create by jack 2017/12/18 */public class CgLibMain { public static void main(String[] args) { HelloServiceCgLib cgLib = new HelloServiceCgLib(); HelloServiceImpl proxyImpl = (HelloServiceImpl) cgLib.getInstance(new HelloServiceImpl()); proxyImpl.sayHello("jack"); }}
这样便实现了CGLIB的动态代理。在mybatis中通常在延迟加载的时候才会用到CGLIB的动态代理。有了这些基础,我们就可以更好的了解mybatis的解析和运行过程了。
源码地址:https://github.com/wj903829182/mybatis_study1
二,构建SqlSessionFactory过程
SqlSessionFactory是Mybatis的核心类之一,其最重要的功能就是提供创建Mybatis的核心接口SqlSession,所以我们需要先创建SqlSessionFactory,为此我们需要提供配置文件和相关的参数。而mybatis是一个复杂的系统,采用构造模式去创建SqlSessionFactory,我们可以通过SqlSessionFactoryBuilder去构建。构建分为两步。
第一步:通过org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件,读取配置参数,并将读取的数据存入这个org.apache.ibatis.session.Configuration类中。注意,mybatis几乎所有的配置都是存在这里的。
第二步:使用Configuration对象去创建SqlSessionFactory。mybatis中的SqlSessionFactory是一个接口,而不是实现类,为此mybatis提供了一个默认的SqlSessionFactory实现类,我们一般都会使用org.apache.ibatis.session.defaults.DefaultSqlSessionFactory。注意,在大部分情况下我们都没有必要自己去创建新的SqlSessionFactory的实现类。
这种创建的方式就是一种Builder模式。对于复杂的对象而言,直接使用构造方法构建是由困难的,这会导致大量的逻辑放在构造方法中,由于对象的复杂性,在构建的时候,我们更希望一步步的来构建它,从而降低其复杂性。这个时候使用一个参数类总领全局,例如,Configuration类,然后分步构建,例如,DefaultSqlSessionFactory类,就可以构建一个复杂的对象,例如,SqlSessionFactory,这种方式值得学习。
1,构建Configuration
在SqlSessionFactory构建中,Configuration是最重要的,它的作用如下:
1)读入配置文件,包括基础配置的xml文件和映射器的xml文件。
2)初始化基础配置,比如mybatis的别名等,一些重要的类对象,例如,插件,映射器,ObjectFactory和typeHandler对象。
3)提供单例,为后续创建SessionFactory服务并提供配置的参数。
4)执行一些重要的对象方法,初始化配置信息。
Confinguration不会是一个很简单的类,mybatis的配置信息都会来自于此。Configuration是通过XMLConfigBuilder去构建的。首先mybatis会读出所有xml配置信息。然后,将这些信息保存到Configuration类的单例中。它会做如下初始化:
1)properties全局参数
2)settings设置
3)typeAliases别名
4)typeHandler类型处理器
5)ObjectFactory对象
6)plugin插件
7)environment环境
8)DatabaseIdProvider数据库标识
9)Mapper映射器
2,映射器的内部组成
一般而言,一个映射器是由三个部分组成:
1)MappedStatement,它保存映射器的一个结点(select|insert|delete|update)。包括许多我们配置的sql,sql的id,缓存信息,resultMap,parameterType,resultType,languageDriver等重要配置内容。
2)SqlSource,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
3)BoundSql,它是建立SQL和参数的地方。它有3个常用的属性:SQL,parameterObject,parameterMappings
上面几个都是映射器的重要内容,也是mybatis的核心内容。在插件的应用中常常会用到它们。映射器的解析过程是比较复杂的,但是在大部分的情况下,我们并不需要去理会解析和组装SQL的规则,因为大部分的插件只要做很小的改变即可,无需做很大的改变。大的改变可能导致重写这些内容。所以一般我们主要关注参数和SQL。
下面看看映射器的内部组成,如下:
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package org.apache.ibatis.mapping;import java.util.ArrayList;import java.util.Collections;import java.util.Iterator;import java.util.List;import org.apache.ibatis.cache.Cache;import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;import org.apache.ibatis.executor.keygen.KeyGenerator;import org.apache.ibatis.executor.keygen.NoKeyGenerator;import org.apache.ibatis.logging.Log;import org.apache.ibatis.logging.LogFactory;import org.apache.ibatis.scripting.LanguageDriver;import org.apache.ibatis.session.Configuration;public final class MappedStatement { private String resource; private Configuration configuration; private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets; MappedStatement() { } public KeyGenerator getKeyGenerator() { return this.keyGenerator; } public SqlCommandType getSqlCommandType() { return this.sqlCommandType; } public String getResource() { return this.resource; } public Configuration getConfiguration() { return this.configuration; } public String getId() { return this.id; } public boolean hasNestedResultMaps() { return this.hasNestedResultMaps; } public Integer getFetchSize() { return this.fetchSize; } public Integer getTimeout() { return this.timeout; } public StatementType getStatementType() { return this.statementType; } public ResultSetType getResultSetType() { return this.resultSetType; } public SqlSource getSqlSource() { return this.sqlSource; } public ParameterMap getParameterMap() { return this.parameterMap; } public List<ResultMap> getResultMaps() { return this.resultMaps; } public Cache getCache() { return this.cache; } public boolean isFlushCacheRequired() { return this.flushCacheRequired; } public boolean isUseCache() { return this.useCache; } public boolean isResultOrdered() { return this.resultOrdered; } public String getDatabaseId() { return this.databaseId; } public String[] getKeyProperties() { return this.keyProperties; } public String[] getKeyColumns() { return this.keyColumns; } public Log getStatementLog() { return this.statementLog; } public LanguageDriver getLang() { return this.lang; } public String[] getResultSets() { return this.resultSets; } /** @deprecated */ @Deprecated public String[] getResulSets() { return this.resultSets; } public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = this.sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(this.configuration, boundSql.getSql(), this.parameterMap.getParameterMappings(), parameterObject); } Iterator var4 = boundSql.getParameterMappings().iterator(); while(var4.hasNext()) { ParameterMapping pm = (ParameterMapping)var4.next(); String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = this.configuration.getResultMap(rmId); if (rm != null) { this.hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; } private static String[] delimitedStringToArray(String in) { return in != null && in.trim().length() != 0 ? in.split(",") : null; } public static class Builder { private MappedStatement mappedStatement = new MappedStatement(); public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) { this.mappedStatement.configuration = configuration; this.mappedStatement.id = id; this.mappedStatement.sqlSource = sqlSource; this.mappedStatement.statementType = StatementType.PREPARED; this.mappedStatement.parameterMap = (new org.apache.ibatis.mapping.ParameterMap.Builder(configuration, "defaultParameterMap", (Class)null, new ArrayList())).build(); this.mappedStatement.resultMaps = new ArrayList(); this.mappedStatement.sqlCommandType = sqlCommandType; this.mappedStatement.keyGenerator = (KeyGenerator)(configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE); String logId = id; if (configuration.getLogPrefix() != null) { logId = configuration.getLogPrefix() + id; } this.mappedStatement.statementLog = LogFactory.getLog(logId); this.mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance(); } public MappedStatement.Builder resource(String resource) { this.mappedStatement.resource = resource; return this; } public String id() { return this.mappedStatement.id; } public MappedStatement.Builder parameterMap(ParameterMap parameterMap) { this.mappedStatement.parameterMap = parameterMap; return this; } public MappedStatement.Builder resultMaps(List<ResultMap> resultMaps) { this.mappedStatement.resultMaps = resultMaps; Iterator var2 = resultMaps.iterator(); while(var2.hasNext()) { ResultMap resultMap = (ResultMap)var2.next(); this.mappedStatement.hasNestedResultMaps = this.mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps(); } return this; } public MappedStatement.Builder fetchSize(Integer fetchSize) { this.mappedStatement.fetchSize = fetchSize; return this; } public MappedStatement.Builder timeout(Integer timeout) { this.mappedStatement.timeout = timeout; return this; } public MappedStatement.Builder statementType(StatementType statementType) { this.mappedStatement.statementType = statementType; return this; } public MappedStatement.Builder resultSetType(ResultSetType resultSetType) { this.mappedStatement.resultSetType = resultSetType; return this; } public MappedStatement.Builder cache(Cache cache) { this.mappedStatement.cache = cache; return this; } public MappedStatement.Builder flushCacheRequired(boolean flushCacheRequired) { this.mappedStatement.flushCacheRequired = flushCacheRequired; return this; } public MappedStatement.Builder useCache(boolean useCache) { this.mappedStatement.useCache = useCache; return this; } public MappedStatement.Builder resultOrdered(boolean resultOrdered) { this.mappedStatement.resultOrdered = resultOrdered; return this; } public MappedStatement.Builder keyGenerator(KeyGenerator keyGenerator) { this.mappedStatement.keyGenerator = keyGenerator; return this; } public MappedStatement.Builder keyProperty(String keyProperty) { this.mappedStatement.keyProperties = MappedStatement.delimitedStringToArray(keyProperty); return this; } public MappedStatement.Builder keyColumn(String keyColumn) { this.mappedStatement.keyColumns = MappedStatement.delimitedStringToArray(keyColumn); return this; } public MappedStatement.Builder databaseId(String databaseId) { this.mappedStatement.databaseId = databaseId; return this; } public MappedStatement.Builder lang(LanguageDriver driver) { this.mappedStatement.lang = driver; return this; } public MappedStatement.Builder resultSets(String resultSet) { this.mappedStatement.resultSets = MappedStatement.delimitedStringToArray(resultSet); return this; } /** @deprecated */ @Deprecated public MappedStatement.Builder resulSets(String resultSet) { this.mappedStatement.resultSets = MappedStatement.delimitedStringToArray(resultSet); return this; } public MappedStatement build() { assert this.mappedStatement.configuration != null; assert this.mappedStatement.id != null; assert this.mappedStatement.sqlSource != null; assert this.mappedStatement.lang != null; this.mappedStatement.resultMaps = Collections.unmodifiableList(this.mappedStatement.resultMaps); return this.mappedStatement; } }}
SqlSource是MappedStatement的一个属性,
public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = this.sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(this.configuration, boundSql.getSql(), this.parameterMap.getParameterMappings(), parameterObject); } Iterator var4 = boundSql.getParameterMappings().iterator(); while(var4.hasNext()) { ParameterMapping pm = (ParameterMapping)var4.next(); String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = this.configuration.getResultMap(rmId); if (rm != null) { this.hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; }
上面的方法是MappedStatement的一个方法,SqlSource是一个接口,它的作用是根据参数和其他的规则组装SQL,包括动态sql。对于参数和sql而言,主要的规则都反映在BoundSql类对象上,在插件中往往需要拿到它进而可以拿到运行的sql和参数规则,做出适当的修改,来满足我们特殊的需求。
BoundSql会提供3个主要的属性:parameterMappings,parameterObject和sql,源码如下:
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package org.apache.ibatis.mapping;import java.util.HashMap;import java.util.List;import java.util.Map;import org.apache.ibatis.reflection.MetaObject;import org.apache.ibatis.reflection.property.PropertyTokenizer;import org.apache.ibatis.session.Configuration;public class BoundSql { private final String sql; private final List<ParameterMapping> parameterMappings; private final Object parameterObject; private final Map<String, Object> additionalParameters; private final MetaObject metaParameters; public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) { this.sql = sql; this.parameterMappings = parameterMappings; this.parameterObject = parameterObject; this.additionalParameters = new HashMap(); this.metaParameters = configuration.newMetaObject(this.additionalParameters); } public String getSql() { return this.sql; } public List<ParameterMapping> getParameterMappings() { return this.parameterMappings; } public Object getParameterObject() { return this.parameterObject; } public boolean hasAdditionalParameter(String name) { String paramName = (new PropertyTokenizer(name)).getName(); return this.additionalParameters.containsKey(paramName); } public void setAdditionalParameter(String name, Object value) { this.metaParameters.setValue(name, value); } public Object getAdditionalParameter(String name) { return this.metaParameters.getValue(name); }}
1)其中parameterObject为参数本身。我们可以传递简单对象,POJO,Map或者@Param注解的参数,由于它在插件中相当常用,需要细细研究。
2)传递简单对象(包括int,String,float,double等),比如当我们传递int类型时,Mybatis会把参数变为Integer对象传递,类型的long,String,float,double也是如此。
3)如果传递的是POJO或者Map,那么这个parameterObject就是你传入的POJO或者Map不变。
4)当然我们也可以传递多个参数,如果没有@Param注解,那么mybatis就会把parameterObject变为一个Map<String,Object>对象,其键值的关系是按顺序来规划的类似于这样的形式{"1":p1,"2":p2,"3":p3..........,"param1":p1,"param2":p2......},所以在编写的时候我们都可以使用#{param1}或者#{1}去引用第一个参数。
5)如果我们使用@Param注解,那么mybatis就会把parameterObject也会变成一个Map<String,Object>对象,类似于没有@Param注解,只是把其数字的键值对应置换为了@Param注解的键值。比如我们注解@Param("key1") String p1,@Param("key2") int p2,@Param("key3") Role p3,那么这个parameterObject对象就是一个Map<String,Object>,它的键值包含:{"key1":p1,"key2":p2,"key3":p3,"param1":p1,"param2":p2,"param3":p3}
6)parameterMappings,它是一个List,每一个元素都是ParameterMapping的对象。这个对象会描述我们的参数。参数包括属性,名称,表达式,javaType,jdbcType,typeHandler等重要信息,我们一般不需要去改变它。通过它可以实现参数和SQL的结合,以便PreparedStatement能够通过它找到parameterObject对象的属性并设置参数,使得查询准确运行。
7)sql属性就是我们书写在映射器里面的一条sql,在大多数的时候无需修改它,只有在插件的情况下,我们可以根据需要进行改写。改写sql将是一件危险的事情,务必小心谨慎。
3,构建SqlSessionFactory
有了Configuration对象构建SqlSessionFactory就很简单了,我们只要写很简单的代码就可以了:
sqlSessionFactory = new SqlSessionFactoryBuilder().builder(inputStream);
mybatis会根据Configuration的配置读取所配置的信息,构建SqlSessionFactory对象。
- mybatis的解析和运行原理1
- MyBatis的解析和运行原理
- mybatis的解析和运行原理2-SqlSession的运行过程
- MyBatis的运行原理
- MyBatis的运行原理
- 【Mybatis学习】Mybatis的解析和运行简要介绍
- mybatis原理解析---SqlSession运行过程(上)
- mybatis原理解析---SqlSession运行过程(下)
- Mybatis框架的运行原理
- Mybatis的基本运行原理
- Mybatis原理学习3:Mybatis的初始化(配置文件的读取和解析)
- MyBatis学习(二):解析MyBatis配置文件的写法和使用原理
- MyBatis学习(三):解析MyBatis的SQL映射XML文件写法和使用原理
- MyBatis六运行原理
- MyBatis 运行原理
- Apache和Nginx运行原理解析
- Apache和Nginx运行原理解析
- Apache和Nginx运行原理解析
- springboot读取配置文件
- 爱情佳作
- five flower classify
- tinyalsa使用
- 关于tomcat的问题,新工程的部署,运行问题
- mybatis的解析和运行原理1
- 微信小程序
- TensorFlow持久化
- Codeforces Round #450 (Div. 2)
- 5G相关知识
- TensorFlow入门案例分析
- TensorFlow技术解析与实战 11 自然语言处理
- 【JZ2440】自我学习记录【如何学习一个新的芯片:S3C2440】
- 图像分割之(五)边缘检测Laplace详解