MyBatis原理

四大步骤:

1、解析配置文件获取DefaultSqlSessionFactory对象

2、创建事务实例和执行器,返回一个DefaultSqlSession对象

3、获取mapper接口的代理对象(mapperProxy)

4、执行增删改查的方法

四大对象:

1、Executor(Executor是一个接口,里面有定义好的增删改查和事务控制的方法)

2、ParameterHandle(封装参数)

3、ResultHandler(封装结果)

4、StatementHanler(执行器,包含增删改查的方法)

流程概述

获取SqlSessionFactoryBuilder对象,解析MySQL配置文件,把解析到的数据保存在configuration中,然后找到mapper.xml文件,解析每一个SQL语句,将增删改查的标签保存在MappedStatement中,一个MappedStatement就代表了一个增删改查,然后返回一个DefaultSqlSessionFactory对象,其中包含了全局配置信息。

使用上一步获取到的DefaultSqlSessionFactory对象,调用openSessionFromDataSource()方法,这个方法主要作用是创建事务实例和执行器(有三种:SIMPLE、REUSE、BATCH,创建执行器的同时需要把事务实例传入,内部会判断一级缓存是否开启,开启的话就返回一个CachingExecutor,返回之前再使用拦截器包装一下)然后返回一个DefaultSqlSession对象

使用上一步获取到的DefaultSqlSession调用getMapper(),使用mapperProxyFactory()创建一个MapperProxy的代理对象,然后创建PrepareStatementHandler对象(有三种:STATEMENT、PERPARED(预编译的)、CALLABLE),将参数包装成一个ParameterHandler,最后调用代理对象的invoke()方法执行增删改查,将返回结果包装成ResultHandler。查询的时候通过selectList()方法,先获取绑定的SQL,然后将查询语句、参数等缓存起来作为一级缓存,查询的时候会判断是否有缓存,有缓存就优先从缓存获取

具体的步骤

1、配置读取

在这里插入图片描述
Configuration封装了所有配置文件的详细信息
总结:把配置文件的信息解析并保存在Configuration对象中,返回包含了Configuration的DefaultSqlSession对象。

(1)创建SqlSessionFactoryBuilder对象

public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		return new SqlSessionFactoryBuilder().build(inputStream);
	}

(2)调用build()方法

public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }

(3)创建xml解析器来解析配置文件

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }

(4)解析每一个标签,把详细信息保存在Configuration

private void parseConfiguration(XNode root) {
        try {
            Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
            this.propertiesElement(root.evalNode("properties"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

此处解析mapper标签单独拿出来看:

private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator i$ = parent.getChildren().iterator();

            while(true) {
                while(i$.hasNext()) {
                    XNode child = (XNode)i$.next();
                    String resource;
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else {
                            if (resource != null || url != null || mapperClass == null) {
                                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

(5)解析mapper.xml标签

private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace != null && !namespace.equals("")) {
                this.builderAssistant.setCurrentNamespace(namespace);
                this.cacheRefElement(context.evalNode("cache-ref"));
                this.cacheElement(context.evalNode("cache"));
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                this.sqlElement(context.evalNodes("/mapper/sql"));
                // 进入
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            } else {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
        } catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
        }
    }
private void buildStatementFromContext(List<XNode> list) {
        if (this.configuration.getDatabaseId() != null) {
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }
        // 进入
        this.buildStatementFromContext(list, (String)null);
    }
public void parseStatementNode() {
        String id = this.context.getStringAttribute("id");
        String databaseId = this.context.getStringAttribute("databaseId");
        if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            Integer fetchSize = this.context.getIntAttribute("fetchSize");
            Integer timeout = this.context.getIntAttribute("timeout");
            String parameterMap = this.context.getStringAttribute("parameterMap");
            String parameterType = this.context.getStringAttribute("parameterType");
            Class<?> parameterTypeClass = this.resolveClass(parameterType);
            String resultMap = this.context.getStringAttribute("resultMap");
            String resultType = this.context.getStringAttribute("resultType");
            String lang = this.context.getStringAttribute("lang");
            LanguageDriver langDriver = this.getLanguageDriver(lang);
            Class<?> resultTypeClass = this.resolveClass(resultType);
            String resultSetType = this.context.getStringAttribute("resultSetType");
            StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
            ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
            String nodeName = this.context.getNode().getNodeName();
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
            boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
            boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
            XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
            includeParser.applyIncludes(this.context.getNode());
            this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
            SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
            String resultSets = this.context.getStringAttribute("resultSets");
            String keyProperty = this.context.getStringAttribute("keyProperty");
            String keyColumn = this.context.getStringAttribute("keyColumn");
            String keyStatementId = id + "!selectKey";
            keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
            Object keyGenerator;
            if (this.configuration.hasKeyGenerator(keyStatementId)) {
                keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
            } else {
                keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            }

            this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
    }

Mapper.xml中的每一个元素信息解析出来并保存在全局配置中,将增删改查标签的每一个标签每一个属性都解析出来,封装成一个MappedStatement,一个MappedStatement就代表一个增删改查标签的所有信息
在这里插入图片描述
(6)上述解析步骤完毕后,返回Configuration,里面包含全局配置文件和运行sql语句需要的所有信息

public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

在这里插入图片描述
(7)调用build(Configuration Configuration)方法,创建一个sqlSessionFactory

public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

(8)返回一个DefaultSqlSessionFactory,里面包含了所有的配置信息

总结:把配置文件的信息解析并保存在Configuration中,返回包含了Configuration的sqlsession对象

2、获取sqlSession对象

在这里插入图片描述
(1)调用openSession()方法获取sqlSession

SqlSession openSession = sqlSessionFactory.openSession();

(2)调用openSessionFromDataSource(…XXX xxx)方法

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

MyBatis中ExecutorType的类型:SIMPLE、REUSE、BATCH
(3)获取一些信息,创建事务

// 代码同上

(4)创建四大对象之一的Executor
(5)根据Executor在全局配置中的类型,创建出对应的Executor

Executor是一个接口,里面有定义好的增删改查和事务控制的方法

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Object executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }
		// 注意此处,看下面说明
        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }

        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }

注意此处:此处是一级缓存的配置,一级缓存默认开启,所以这里就创建了一个CachingExecutor,查询之前先查一下缓存

(6)如果有二级缓存配置,创建CachingExecutor(executor)

(7)使用拦截器来包装executor并返回

public Object pluginAll(Object target) {
        Interceptor interceptor;
        for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
            interceptor = (Interceptor)var2.next();
        }

        return target;
    }

(8)(9)以上流程完成后,返回一个DefaultSqlSession,包含Configuration和Executor

总结:返回了sqlSession的实现类DefaultSqlSession对象,他里面包含了Executor和Configuration

3、获取接口的代理对象

在这里插入图片描述
(1)调用SqlSession的getMapper(xxx.Class)方法

EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);

(2)调用configuration.getMapper(type, this)

public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

(3)

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

(4)根据接口类型获取MapperProxyFactory

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
            	// 第5步
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

(5)创建实例


(6)创建MapperProxy

public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
public class MapperProxy<T> implements InvocationHandler, Serializable {

	}

(7)创建mapperProxy的代理对象

protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

(8)返回mapperProxy的代理对象

// 这里的mapper就是代理对象
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);

在这里插入图片描述
总结:调用getMapper()的时候,使用mapperProxyFactory创建一个MapperProxy的代理对象,代理对象包含了DefaultSqlSession(Exetot)

4、

在这里插入图片描述
(1)调用代理对象的invoke()方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }

调用invoke()的时候,会调用cachedMapperMethod()创建一个MapperMethod,然后调用它的execute()方法

(2)判断增删改查类型,如果是SELECT方法,会根据返回类型执行不同的方法

public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
            	// 第3步
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

(3)包装参数为一个map或者直接返回,用于下一步方法的参数
(4)调用selectOne()方法
(5)调用DefaultSqlSession的selectList()方法

public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }

注意:虽然是selectOne()方法,但是调用的仍然是DefaultSqlSession的selectList()方法,只不过返回的是只返回一个

(6)获取MappedStatement
(7)调用executor的query方法

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }

分析selectList()方法是如何执行的

(8)获取BoundSql,它代表sql语句的详细信息(sql语句、参数、参数和值得对应关系…)

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

在这里插入图片描述
这里会将sql语句创建成缓存的key,这个key由方法id+sql+参数+…组成,总之就是很长

(9)调用executor的query()方法。(注意下面代码中对二级缓存的判断,如果有缓存就使用CachingExecuor,没有的话就使用SimpleExecetor)

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 (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator i$ = this.deferredLoads.iterator();

                while(i$.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

(10)查看本地缓存是否有数据,没有就调用queryFromDatabase,查出以后也会保存在本地缓存

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 (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator i$ = this.deferredLoads.iterator();

                while(i$.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

(11)调用BaseExecutor的doQuery()方法

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }

        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }

(12)创建四大对象之一的PrepareStatementHandler对象,这个过程中会有个判断确定返回的是哪一种handler,一共有三种:STATEMENT、PREPARED、CALLABLE,默认是PREPARED(预编译)
(13)使用拦截器来包装创建的StatementHandler
(14)使用拦截器来包装创建的ParameterHandler
(15)使用拦截器来包装创建的ResultHandler
(16)预编译sql产生PreparedStatement对象
(17)调用ParameterHandler来设置参数
(18)调用TypeHandler来预编译设置参数
(19)查出数据使用ResultHandler处理结果
(20)资源关闭
(21)返回list的第一个

所有流程总结:

1、根据配置文件(全局,sql映射)初始化出Configuration对象
2、创建一个DefaultSqlSession对象,他里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
3、DefaultSqlSession.getMapper()方法,拿到Mapper接口对应的MapperProxy;
4、MapperProxy里面有(DefaultSqlSession)
5、执行增删改查方法:
1)调用DefaultSqlSession的增删改查(Executor)
2)会创建一个StatementHandler对象
3)调用StatementHandler预编译参数以及设置参数值;使用ParameterHandler来给sql设置参数
4)调用StatementHandler的增删改查方法
5)ResultSetHandler封装结果

注意:四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler)

Mybatis面试18问

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值