参考资料:MyBatis原理深入解析
整体架构
架构设计
Mybatis按照功能可以划分为三层:
- **API接口层:**对外提供操作数据库的api。mybatis和数据库的交互有两种方式,使用sqlSession的api,或者使用Mapper代理的方式;
- **数据处理层:**负责参数设置、SQL解析和执行,以及结果映射
- **基础支撑层:**提供最基础的功能支撑,包括数据库连接、事务管理、缓存配置等等
层次结构
mybatis通过sqlSession完成一次sql操作,需要经历以下几个步骤。
SqlSession方式访问
mybatis提供了两种方式让我们去访问数据库,一种是通过调用sqlSession的api,另一种是通过获取mapper接口的代理对象,直接调用其方法。接下来,我们先分析sqlSession如何完成与数据库的交互。
public void test1() throws IOException {
// 1. 读取配置文件,转成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. 解析配置文件,封装Configuration对象 创建DefaultSqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 创建DefaultSqlsession实例对象 设置事务不自动提交 完成executor对象的创建
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.(1)根据statementid,从Configuration中map集合中获取到了指定的MappedStatement对象
// (2)将查询任务委派了executor执行器
List<Object> objects = sqlSession.selectList("namespace.id");
// 5.释放资源
sqlSession.close();
}
总结
由于调用过程比较复杂,因此,先简单说明下整体思路,这样不容易乱。
主线脉络:SqlSessionFactory -> SqlSession -> Executor -> StatementHandler -> ParameterHandler -> ResultSetHandler
**1.SqlSessionFactory:**通过XMLConfigBuilder解析配置文件各种标签,把配置信息保存在
Configuration对象,调用有参构造方法,传递Configuration对象,创建DefaultSqlSessionFactory**2.SqlSession#创建对象:**调用SqlSessionFactory#openSession方法,先新建一个SimpleExecutor对象,执行有参构造方法,传入executor和configuration,实例化DefaultSqlSession对象
**3.SqlSession#查询:**根据statement id,即
namespace+id,从configuration中获取对应的mappedStatement,交给executor,由执行器完成查询操作4.Executor#查询:
- 根据传参,把
#{}替换成?占位符,解析生成动态sql- 建立查询缓存
- 创建StatementHandler对象,把查询操作委派给它
5.StatementHandler#查询:
创建Statement对象
由
ParameterHandler完成参数设置,替换?占位符为参数值Statement调用execute方法,执行查询操作
由
ResultSetHandler处理结果集,如果是selectList操作,则返回List集合
会话工厂
第1~2步是mybatis的创建会话工厂过程,读取配置文件,把配信息保存到Configuration对象中。第1步是读取配置文件,转成字节输入流,这个过程我们就不看了,直接去探究怎么创建会话工厂。
SqlSessionFactory
SqlSessionFactoryBuilder#build(java.io.InputStream)
public SqlSessionFactory build(InputStream inputStream) {
//调用了重载方法
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建 XMLConfigBuilder(专门解析mybatis配置文件的类)
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 执行 XML 解析。parser.parse()返回值为Configuration对象
// 再调用重载方法,build
return build(parser.parse());
}
... 省略部分代码
}
在build方法中,创建XMLConfigBuilder对象,用于解析转成字节流的配置信息,保存到Configuration对象。接着,再次调用build重载方法。
Configuration
由于Configuration比较重要,先简单介绍下。
Configuration对象用于保存配置文件信息,它的结构和xml配置文件几乎相同,配置文件的每个标签都有对应的内部属性。
xml标签:settings、typeAliases、objectFactory、mapper等
Configuration部分属性
/**
* 变量 Properties 对象。
*
* 参见 {@link org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement(XNode context)} 方法
*/
protected Properties variables = new Properties();
/**
* ReflectorFactory 对象
*/
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
/**
* ObjectFactory 对象
*/
protected ObjectFactory objectFactory = new DefaultObjectFactory();
XMLConfigBuilder#parse
XMLConfigBuilder#parse
/**
* 解析 XML 成 Configuration 对象。
*
* @return Configuration 对象
*/
public Configuration parse() {
... 省略部分代码
// 解析 XML configuration 节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
调用parseConfiguration方法解析mybatis配置文件中的各种标签,并把配置信息把保存到Configuration对象
/**
* 解析 XML
*
* 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
*
* @param root 根节点
*/
private void parseConfiguration(XNode root) {
try {
... 各种标签,省略
// 赋值 <settings /> 到 Configuration 属性
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析 <environments /> 标签
environmentsElement(root.evalNode("environments"));
// 解析 <mappers /> 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
MappedStatement
在解析配置文件时,<insert>、<update>等标签会被封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements属性中,mappedStatements是一个HashMap,key是statement id,即namespace + id,value是MappedStatement对象。
<configuration>
<!--引入映射配置文件-->
<mappers>
<mapper resource="UserMapper.xml"></mapper>
</mappers>
</configuration>
<!--namespace : 名称空间:与id组成sql的唯一标识-->
<mapper namespace="com.lagou.dao.IUserDao">
<!--添加用户-->
<!--parameterType:参数类型-->
<insert id="saveUser" parameterType="user" >
insert into user values(#{id},#{username})
</insert>
</mapper>
/**
* MappedStatement 映射
*
* KEY:`${namespace}.${id}`
*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
DefaultSqlSessionFactory
至此,xml文件解析完成,调用build方法,创建默认的会话工厂。SqlSessionFactoryBuilder#build
/**
* 创建 DefaultSqlSessionFactory 对象
*
* @param config Configuration 对象
* @return DefaultSqlSessionFactory 对象
*/
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
总结
创建SqlSessionFactory时,先读取配置文件,转成字节输入流。接着,通过
XMLConfigBuilder#parseConfiguration方法解析xml标签,把配置信息保存到Configuration对象的内部属性中,对于mapper文件中sql,会生成对应的MappedStatement,存储到HashMap中,key是mapper文件命名空间+方法名。最后,调用有参构造方法,传入Configuration对象,创建DefaultSqlSessionFactory
SqlSession
SqlSession是Mybatis用来与数据库交互的顶层接口,通常将它与ThreadLocal绑定,一次会话使用同一个SqlSession,使用完还需要关闭。有两个实现类:DefaultSqlSession(默认)和SqlSessionManager(弃用)。
DefaultSqlSession有两个重要参数,configuration保存解析后配置信息,executor为执行器,各种sql操作都是委派给执行器完成。
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
...
}
创建对象
刚刚我们已经分析完第1~2步,接下来就是第3步,通过会话工厂的openSession()获取sqlSession。
public void test1() throws IOException {
......
// 3. 创建DefaultSqlsession实例对象 设置事务不自动提交 完成executor对象的创建
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.(1)根据statementid,从Configuration中map集合中获取到了指定的MappedStatement对象
// (2)将查询任务委派了executor执行器
List<Object> objects = sqlSession.selectList("namespace.id");
// 5.释放资源
sqlSession.close();
}
DefaultSqlSessionFactory#openSession()
@Override
public SqlSession openSession() {
//getDefaultExecutorType()传递的是SimpleExecutor
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建 Executor 对象
final Executor executor = configuration.newExecutor(tx, execType);
// 创建 DefaultSqlSession 对象
return new DefaultSqlSession(configuration, executor, autoCommit);
}
... 省略部分代码
}
调用api
拿到会话后,我们就可以调用相关的api操作数据库,继续执行第4步
// 4. (1)根据statementid,从Configuration中map集合中获取到了指定的MappedStatement对象
// (2)将查询任务委派了executor执行器
List<Object> objects = sqlSession.selectList("namespace.id");
查看sqlSession的selectList实现方法,DefaultSqlSession#selectList
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获得 MappedStatement 对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行查询 RowBounds -> 用于逻辑分页 wrapCollection(parameter) -> 用来装饰数组或集合
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
... 省略部分代码
}
可以看到,selectList方法,根据statement id,从configuration的mappedStatements中获取对应的mappedStatement对象,把他交给执行器,由执行器完成查询操作。
总结
创建对象:会话工厂调用openSession方法,内部会执行DefaultSqlSession的有参构造方法,设置configuration、executor属性。
调用api:通过
namespace+id从congfiguration中获取对应的mappedStatement,把它交给executor,由执行器完成相关数据库操作
Executor
在前面创建SqlSession时,我们传递的执行器是SimpleExecutor,接下来找到对应的query方法。但是,由于SimpleExecutor继承于BaseExecutor,本身自己并没有重写query方法,所以执行的是BaseExecutor#query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
// 根据传入的参数,动态解析SQL语句,把#{}替换成?,返回BoundSql,提供给后续的PrepareStatement使用
BoundSql boundSql = ms.getBoundSql(parameter);
// 为本次查询创建缓存的Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
... 省略部分代码
try {
// 从一级缓存中,获取查询结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 获取不到,从数据库中读取
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
... 省略部分代码
return list;
}
// 从数据库中读取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 执行读操作
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
... 省略部分代码
return list;
}
继续往下执行,调用SimpleExecutor#doQuery方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
... 省略部分代码
Configuration configuration = ms.getConfiguration();
// 创建StatementHanlder对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter,
rowBounds, resultHandler, boundSql);
// 创建jdbc中的statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// StatementHandler执行读操作
return handler.query(stmt, resultHandler);
}
经过多个方法嵌套调用,Executor#query方法最终会创建一个StatementHandler对象,把查询操作委派给StatementHandler完成。
总结
Executor执行过程:
1.根据传参,完成sql语句的动态解析,将#{}替换成?,生成BoundSql对象,供StatementHandler对象使用
2.为提高性能,创建查询缓存
3.创建StatementHandler,由StatementHandler完成数据库的查询操作
StatementHandler
StatementHandler主要完成两个工作:
- 参数设置:通过调用ParameterHandler来完成sql参数设置,替换占位符
- 结果集封装:通过使用ResultSetHandler封装Statement返回的结果集
参数设置
继续从doQuery方法跟踪代码
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
... 省略部分代码
// 创建jdbc中的statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// StatementHandler执行读操作
return handler.query(stmt, resultHandler);
}
// 创建PrepareStatement对象,初始化StatementHandler对象
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获得 Connection 对象
Connection connection = getConnection(statementLog);
// 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
return stmt;
}
@Override
public void parameterize(Statement statement) throws SQLException {
//使用ParameterHandler对象来完成对Statement的设值
parameterHandler.setParameters((PreparedStatement) statement);
}
parameterize方法调用ParameterHandler#setParameters,根据传入参数,把statement对象的?占位符替换成参数值
结果集封装
进入handler#query方法,查看里面执行过程。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
... 省略部分代码
// StatementHandler执行读操作
return handler.query(stmt, resultHandler);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
// 执行查询
statement.execute(sql);
// 处理返回结果
return resultSetHandler.handleResultSets(statement);
}
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
// 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获得 ResultMap 数组
// 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount); // 校验
while (rsw != null && resultMapCount > resultSetCount) {
// 获得 ResultMap 对象
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理 ResultSet ,将结果添加到 multipleResults 中
handleResultSet(rsw, resultMap, multipleResults, null);
// 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
rsw = getNextResultSet(stmt);
// 清理
cleanUpAfterHandlingResultSet();
// resultSetCount ++
resultSetCount++;
}
... 省略部分代码
// 如果是 multipleResults 单元素,则取首元素返回
return collapseSingleResultList(multipleResults);
}
执行器调用query方法,实际上是JDBC的Statement对象执行sql,然后使用ResultSetHandler把结果集转成List集合。
总结
StatementHandler主要完成两件事情:
1.由ParameterHandler完成参数设置,把sql中
?占位符替换成参数值2.statement执行sql语句,由ResultSetHandler处理结果集,如果是查询操作,转成List集合
Mapper方式访问
除了直接调用SqlSession的api操作数据库外,mybatis还提供通过Mapper接口的代理对象,完成与数据库的交互。
/**
* mapper代理方式
*/
public void test2() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
// 使用JDK动态代理对mapper接口产生代理对象
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
//代理对象调用接口中的任意方法,执行的都是动态代理中的invoke方法
List<Object> allUser = mapper.findAllUser();
}
MapperRegistry
在开始分析Mapper方式前,先介绍MapperRegistry,它是Configuration的一个属性,内部维护一个HashMap,key是mapper接口的class对象,value是每个接口对应的代理工厂类。
/**
* MapperRegistry 对象
*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public class MapperRegistry {
/**
* MyBatis Configuration 对象
*/
private final Configuration config;
/**
* MapperProxyFactory 的映射
*
* KEY:Mapper 接口
*/
// 这个类中维护一个HashMap存放MapperProxyFactory
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
}
在解析mappers标签时,会判断配置的是xml文件,还是Mapper接口或者包名。
<mappers>
<mapper resource="UserMapper.xml"></mapper>
<package name="com.drh.mapper"/>
<mapper class="com.drh.mapper.UserMapper"></mapper>
</mappers>
-
如果是xml文件,会解析
<insert>、<select>等标签,封装成MappedStatement对象,存储到Configuration的mappedStatement -
如果是接口或包名,会给mapper接口创建一个对应的MapperProxyFactory对象,存储到Map,key是接口的class对象,value是代理工厂类
-
org.apache.ibatis.binding.MapperRegistry#addMapper -
public <T> void addMapper(Class<T> type) { // 判断,必须是接口。 if (type.isInterface()) { // 已经添加过,则抛出 BindingException 异常 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 添加到 knownMappers 中 knownMappers.put(type, new MapperProxyFactory<>(type)); ... 省略部分代码 } }
-
getMapper
开始分析,进入DefaultSqlSession#getMapper方法
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获得 MapperProxyFactory 对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 不存在,则抛出 BindingException 异常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 返回代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
org.apache.ibatis.binding.MapperProxyFactory#newInstance
public T newInstance(SqlSession sqlSession) {
// 创建 实现了JDK动态代理的invocationHandler接口的mapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用了重载方法
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 调用JDK动态代理创建Mapper代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
调用Mapper方法
既然我们已经知道在getMapper时,返回的是mapper接口的代理对象,因此,调用mapper接口的方法时,会被代理对象拦截到。根据前面的分析,这个拦截方法就是MapperProxy#invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
... 省略部分代码
// 获得 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 重点在这:MapperMethod最终调用了执行的方法
return mapperMethod.execute(sqlSession, args);
}
MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断mapper中的方法类型,最终调用的还是SqlSession中的方法
switch (command.getType()) {
case INSERT: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行 INSERT 操作
// 转换 rowCount
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 无返回,并且有 ResultHandler 方法参数,则将查询的结果,提交给 ResultHandler 进行处理
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// 执行查询,返回列表
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// 执行查询,返回 Map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
// 执行查询,返回 Cursor
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
// 执行查询,返回单个对象
} else {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 查询单条
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
... 省略部分代码
// 返回结果
return result;
}
在MapperProxy#invoke方法中,创建MapperMethod对象,并调用其execute方法。通过匹配mapper方法的类型(增/删/改/查),最终还是调用sqlSession的api完成数据库操作。
总结
Mapper方式访问数据库,主要分为两步:获取代理对象,和执行接口方法
- 获取代理对象:
- 解析
mapper标签,如果指定的是接口或者包名,MapperRegistry会给每个接口创建一个代理工厂类,同时使用一个HashMap保存这个对应关系,key是接口的class对象- 调用getMapper,经过多层方法调用(sqlSession -> configuration -> mapperRegistry),获取到Mapper接口对应的
MapperProxyFactory,然后调用newInstance方法。创建实现了InvocationHandler接口的MapperProxy对象,最后通过JDK动态代理,创建Mapper接口的代理对象- 执行接口方法:调用mapper方法时,实际是执行
MapperProxy#invoke方法。创建MapperMethod对象,调用其execute方法,匹配mapper方法的类型,最终选择SqlSession对应的api完成数据库操作。