MyBatis是一款ORM框架,它解决的问题是针对JDBC操作数据库和封装数据集繁琐的问题。下面我们看一下传统JDBC来创建连接和获取数据集的方式。
JDBC
maven依赖
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.22</version>
</dependency>
public void testjdbc() throws Exception {
// 连接
Connection connection = null;
// 执行者
PreparedStatement preparedStatement = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 创建连接
connection = DriverManager.getConnection("jdbc:mysql://xx.xx.xx.xx:3306/test", "root", "xxxxxx");
String sql = "select id, name, age from user where id=?";
// 创建执行者 -- 执行SQL,携带参数
preparedStatement = connection.prepareStatement(sql);
// 参数索引 表示SQL第几个?号参数,值为1
preparedStatement.setInt(1,1);
// 执行sql
preparedStatement.execute();
// 获取结果集
ResultSet resultSet = preparedStatement.getResultSet();
while (resultSet.next()){
// 封装对象
User user = User.builder()
.id(resultSet.getInt("id"))
.name(resultSet.getString("name"))
.age(resultSet.getInt("age"))
.build();
System.out.println(user.toString());
}
}finally {
// 释放资源
if(connection!=null){
connection.close();
}
if(preparedStatement!=null){
preparedStatement.close();
}
}
}
从这可以看出整个JDBC连接数据库和数据集获取都是有一定的弊端,比如说有以下几点弊端
- 数据库连接每连接一个就要走连接-释放过程,这是非常耗性能的。这里可以利用池化思想,建立一个数据库连接池,创建的连接可以放进数据库连接池中,释放的时候放回到连接池,避免重新创建
- 硬编码太多,比如SQL语句,数据库驱动,数据库连接这些都是硬编码,其实这种都可以放进配置文件
- 创建执行者
PreparedStatement时,如果我的SQL语句发生了变化,下面的参数设值也会发生变化,修改起来比较麻烦 - 从结果集中取数据这里如果我表字段类型变了,这里也是需要做调整,不易维护
上面的问题MyBatis都可以解决
- MyBatis可以配置数据连接池,比如Druid,Hikari,使用连接池管理数据库连接
- 将Sql语句配置在XXXXmapper.xml文件中与java代码分离
- MyBatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型
- MyBatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型
MyBatis Demo
db属性配置文件db.properties
mysql.driverClass=com.mysql.jdbc.Driver
mysql.jdbcUrl=jdbc:mysql://xx.xx.xx.xx:3306/test?characterEncoding=utf8
mysql.user= root
mysql.password= xxxxxx
MyBatis配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--properties 扫描属性文件db.properties -->
<properties resource="db.properties"></properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--mybatis内置了JNDI、POOLED、UNPOOLED三种类型的数据源,
其中POOLED对应的实现org.apache.ibatis.datasource.pooled.PooledDataSource-->
<dataSource type="POOLED">
<property name="driver" value="${mysql.driverClass}"/>
<property name="url" value="${mysql.jdbcUrl}"/>
<property name="username" value="${mysql.user}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.dm.mapper"/>
</mappers>
</configuration>
实体类
@Data
@Builder
public class User {
private Integer id;
private String name;
private Integer age;
}
Mapper接口
public interface UserMapper {
User selectById(@Param(value = "id") Integer id);
List<User> selectAllUser();
}
Mapper xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dm.mapper.UserMapper">
<!-- 二级缓存作用域-->
<cache></cache>
<resultMap id="result" type="com.dm.entity.User">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="user_name" jdbcType="VARCHAR" property="name"/>
<result column="age" jdbcType="INTEGER" property="age"/>
</resultMap>
<select id="selectById" resultMap="result">
select * from user where id=${id}
<where>
<if test="id > 0">
and id=#{id}
</if>
</where>
</select>
<select id="selectAllUser" resultType="com.dm.entity.User">
select * from user
</select>
</mapper>
测试代码
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
Reader reader = null;
SqlSession session = null;
SqlSession session1 = null;
try {
//读取xml文件
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory--DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// DefaultSqlSession
// 1查询
session = sqlMapper.openSession();
session1 = sqlMapper.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
// 2查询
UserMapper mapper1 = session1.getMapper(UserMapper.class);
// 输出Cache Hit Ratio判断是否走二级缓存,
// session.commit();注释掉走了数据库,没注释没走数据库
System.out.println(mapper.selectById(1));
session.commit();
System.out.println(mapper1.selectById(1));
} finally {
if (reader != null) {
reader.close();
}
if (session != null) {
session.close();
}
if (session1 != null) {
session1.close();
}
}
}
源码分析
初始化Configuration
new SqlSessionFactoryBuilder().build(read)
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.
}
}
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
/**
* 调用父类的BaseBuilder的构造方法
* configuration赋值
* typeAliasRegistry别名注册器赋值
* typeAliases Map中
*/
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
/**
* 把props绑定到configuration的props属性上
*/
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
解析mybatis-config.xml文件
parser.parse()
public Configuration parse() {
// 若已经解析过了 就抛出异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 设置解析标志位
parsed = true;
// 解析我们的mybatis-config.xml的configuration节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parseConfiguration
解析我们的mybatis-config.xml的configuration节点
private void parseConfiguration(XNode root) {
try {
/**
* 解析 properties节点
* <properties resource="mybatis/db.properties" />
* 解析到org.apache.ibatis.parsing.XPathParser#variables
* org.apache.ibatis.session.Configuration#variables
*/
propertiesElement(root.evalNode("properties"));
/**
* 解析我们的mybatis-config.xml中的settings节点
* 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
* <settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="cacheEnabled" value="true"/>
..............
</settings>
*
*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
/**
* VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
* Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
* 解析到:org.apache.ibatis.session.Configuration#vfsImpl
*/
loadCustomVfs(settings);
/**
* 指定 MyBatis 所用日志的具体实现,未指定时将自动查找
* SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
* 解析到org.apache.ibatis.session.Configuration#logImpl
*/
loadCustomLogImpl(settings);
/**
* 解析我们的别名
* <typeAliases>
<typeAlias alias="User" type="com.dm.entity.User"/>
</typeAliases>
<typeAliases>
<package name="com.dm"/>
</typeAliases>
解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
*/
typeAliasesElement(root.evalNode("typeAliases"));
/**
* 解析我们的插件(比如分页插件)
* mybatis自带的4大插件 一般都使用Executor
* Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
*/
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 设置settings 和默认值
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
/**
* 解析我们的mybatis环境
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="root"/>
<property name="password" value="Zw726515"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
* 解析到:org.apache.ibatis.session.Configuration#environment
* 在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
*/
environmentsElement(root.evalNode("environments"));
/**
* 解析数据库厂商
* <databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="MySql" value="mysql" />
</databaseIdProvider>
* 解析到:org.apache.ibatis.session.Configuration#databaseId
*/
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/**
* 解析我们的类型处理器节点
* <typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
*/
typeHandlerElement(root.evalNode("typeHandlers"));
// ===========================重点===========================
/**
* 解析我们的mapper
*
resource:来注册我们的class类路径下的
class:使用接口
url:来指定我们磁盘下的或者网络资源的
package:将包内的映射器接口实现全部注册为映射器
class:
若注册Mapper不带xml文件的,这里可以直接注册
若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
<mappers>
<mapper resource="com/dm/mapper/UserMapper.xml"/>
<mapper class="com.dm.mapper.UserMapper"></mapper>\
<mapper url="file:///var/mappers/UserMapper.xml"></mapper>
<package name="com.dm.mapper"></package>
</mappers>
*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
构建二级缓存
cacheElement
解析二级缓存
private void cacheElement(XNode context) {
if (context != null) {
//解析cache节点的type属性
String type = context.getStringAttribute("type", "PERPETUAL");
//根据type的String获取class类型
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//获取缓存过期策略:默认是LRU
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
//flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
Long flushInterval = context.getLongAttribute("flushInterval");
//size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
Integer size = context.getIntAttribute("size");
//只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//把缓存节点加入到Configuration中
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
useNewCache
构建二级缓存
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
public Cache build() {
// implementation -- PerpetualCache
// decorators -- LruCache
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 = new SerializedCache(cache)
// cache = new LoggingCache(cache);
// cache = new SynchronizedCache(cache);
// 通过责任链构建装饰器 PerpetualCache -> LruCache -> SerializedCache -> LoggingCache -> SynchronizedCache
// 调用 SynchronizedCache -> LoggingCache -> SerializedCache -> PerpetualCache -> LruCache
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
解析**Mapper.xml文件
如果是批量扫描也就是mapper使用package模式会调用addMapper方法,往里面跟到MapperAnnotationBuilder.parse()下的loadXmlResource也是用下面的方法进行的xml解析
如果是用得注解形式SQL在parseStatement解析成MappedStatement
mapperElement
解析我们的mybatis-config.xml的mapper节点
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 获取我们mappers节点下的一个一个的mapper节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 真正的解析我们的mapper.xml配置文件,解析SQL
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
这段代码核心点其实就是构建一个XMLMapperBuilder调用parse方法解析SQL
XMLMapperBuilder.parse
public void parse() {
// 判断当前的Mapper是否被加载过
if (!configuration.isResourceLoaded(resource)) {
// 真正的解析我们的 <mapper namespace="com.dm.mapper.UserMapper">
// ================ 这个方法重要 ================
configurationElement(parser.evalNode("/mapper"));
// 把资源保存到我们Configuration中
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement
解析**Mapper.xml文件mapper节点
private void configurationElement(XNode context) {
try {
/**
* 解析我们的namespace属性
* <mapper namespace="com.dm.mapper.UserMapper">
*/
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
/**
* 保存我们当前的namespace 并且判断接口完全类名==namespace
*/
builderAssistant.setCurrentNamespace(namespace);
/**
* 解析我们的缓存引用
* 说明我当前的缓存引用和DeptMapper的缓存引用一致
* <cache-ref namespace="com.dm.mapper.DeptMapper"></cache-ref>
解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
*/
cacheRefElement(context.evalNode("cache-ref"));
/**
* 解析我们的cache节点
* <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
解析到:org.apache.ibatis.session.Configuration#caches
org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
*/
cacheElement(context.evalNode("cache"));
/**
* 解析paramterMap节点
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
/**
* 解析我们的resultMap节点
* 解析到:org.apache.ibatis.session.Configuration#resultMaps
* 异常 org.apache.ibatis.session.Configuration#incompleteResultMap
*/
resultMapElements(context.evalNodes("/mapper/resultMap"));
/**
* 解析我们通过sql节点
* 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
* 其实等于 org.apache.ibatis.session.Configuration#sqlFragments
* 因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
*/
sqlElement(context.evalNodes("/mapper/sql"));
/**
* 解析我们的select | insert |update |delete节点
* 解析到org.apache.ibatis.session.Configuration#mappedStatements
*/
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
创建SqlSession
通过SqlSessionFactory.openSession()创建SqlSession对象
openSessionFromDataSource
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);
/**
* 创建一个sql执行器对象
* 一般情况下 若我们的mybaits的全局配置文件的cacheEnabled默认为ture就返回
* 一个cacheExecutor,若关闭的话返回的就是一个SimpleExecutor
*/
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();
}
}
newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//批量的执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
//可重复使用的执行器
executor = new ReuseExecutor(this, transaction);
} else {
//简单的sql执行器对象
executor = new SimpleExecutor(this, transaction);
}
//判断mybatis的全局配置文件是否开启缓存
if (cacheEnabled) {
//把当前的简单的执行器包装成一个CachingExecutor
executor = new CachingExecutor(executor);
}
//调用所有的拦截器对象plugin方法
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
从上诉代码可以发现Executor共有4种BatchExecutor,ReuseExecutor,SimpleExecutor,CachingExecutor
如果开启了二级缓存Executor就是CachingExecutor包装了一个其他三种中的Executor,没有开启就是其他三种中的一种
Mapper执行流程
Mapper在SqlSession.getMapper中获取到了Mapper代理对象MapperProxy,MapperProxy中保存了sqlSession, mapperInterface, methodCache参数,包含sqlSession,接口,和方法,到时候直接反射通过接口和方法就可以调到指定的SQL语句
直接来到MapperProxy.invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 判断我们的方法是不是我们的Object类定义的方法,若是直接通过反射调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
// 是否接口的默认方法
} else if (method.isDefault()) {
// 调用我们的接口中的默认方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 调用
return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断我们执行sql命令的类型
switch (command.getType()) {
//insert操作
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
//update操作
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
//delete操作
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
//select操作
case SELECT:
//返回值为空
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//返回值是一个List
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回值是一个map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
//返回游标
result = executeForCursor(sqlSession, args);
} else {
//查询返回单个
//解析我们的参数
Object param = method.convertArgsToSqlCommandParam(args);
//执行sql
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());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
具体查询流程
这里我们查询的是selectById所以我们这里会执行sqlSession.selectOne(),在底层selectOne还是会调selectList方法只是取了第一个
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
/**
* 通过我们的statement去我们的全局配置类中获取MappedStatement
*/
MappedStatement ms = configuration.getMappedStatement(statement);
/**
* 如果前面用插件代理了executor这里会首先执行Plugin代理类,执行拦截器调用Plugin.invoke
*
* 通过执行器去执行我们的sql对象包装我们的集合类参数
* 一般情况下是executor为CachingExecutor对象
*/
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();
}
}
上面的 executor.query这里具体要看是哪个executor,在newExecutor方法中有体现。开启了二级缓存是CachingExecutor没有开启是BaseExecutor,这里我们走的是CachingExecutor
如果使用的是CachingExecutor就会直接在二级缓存拿,二级缓存没拿到就调用delegate拿到封装前的类,封装前的Executor有3种,但他们都没有实现query方法就会调基类的query,这里就会调用BaseExecutor.query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//通过参数对象解析我们的sql详细信息
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// CachingExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 判断我们我们的mapper中是否开启了二级缓存<cache></cache>
Cache cache = ms.getCache();
// 判断是否配置了cache
if (cache != null) {
// 判断是否需要刷新缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 先去二级缓存中获取
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
// 二级缓存中没有获取到
if (list == null) {
//通过查询数据库去查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//加入到二级缓存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//没有整合二级缓存,直接去查询
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// BaseExecutor#query
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());
//已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空本地缓存,如果queryStack为零,并且要求清空本地缓存。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// 从一级缓存中,获取查询结果
queryStack++;
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);
}
} 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;
}
在BaseExecutor中queryFromDatabase走了doQuery,这个方法由创建的具体Executor类实现有BatchExecutor,ReuseExecutor,SimpleExecutor三种,这里我们进入SimpleExecutor
SimpleExecutor#doQuery
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();
// 内部封装了ParameterHandler和ResultSetHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
从这一部分代码可以看出插件代理调用顺序 Executer->StatementHandler->ParameterHandler->ResultSetHandler