(四)MyBatis源码解析之SqlSession
2016-06-02 17:00
786 查看
上篇博文将完了MyBatis的配置文件解析的过程,从这篇博文开始分析MyBatis的执行流程
解析完配置文件之后,配置文件中的所有信息就存储到了Configuration对象中了,在创建SqlSessionFactory对象时将Configuration做为SqlSessionFactory对象的一个属性,通过调用SqlSessionFactory的openSession(...),openSession()有很多重载方法,创建SqlSession对象只能通过openSession()和这些重载方法
executorType有三种类型,分别是BATCH,REUSE,SIMPLE,默认的是SIMPLE类型,这三种类型分别对应着三种executor,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor就是简单的执行sql操作,除了这三种类型之外Mybatis还支持CacheExecutor,这就是Mybatis内建的缓存机制,在SqlSession级别提供对SQL查询结果的缓存,即:如果你调用了相同的select查询,Mybatis会将放在缓存的结果返回,而不会去再查询数据库,后面具体分析查询过程时会分析Mybatis是如何实现缓存的,在Mybatis中真正负责执行数据库操作的就是这些Executor
通过代理获得Mapper
Mybatis建议通过mapper对象访问mybatis,使用mapper和使用普通的方式的区别主要有以下几点
1、普通的方式在dao层查询的id和namespace是手写的,和xml中的配置文件有可能会对不上,实际工作中经常会碰到这种状况,而使用mapper的时候每个接口在哪个包下是固定的不会随意改变,省去了我们写包名时候容易导致的错误
2、普通的方式入参只能有一个,如果sql需要多个参数,我们需要先把多个参数封装成一个对象或者是放到map中,而使用mapper的时候可以使用任意多个参数
知道了通过Mapper编程的好处之后,思考一个问题mapper是一个接口,没有实现类怎么可以调用该接口的方法呢?答案就是动态代理,使用sqlSession的getMapper(...)方法,在调用该方法的时候传我们需要的Mapper接口的class对象进去,mybatis就可以为我们创建该对象的实现类了,具体是怎么创建的呢,一起来看看
到目前为止我们已经找到了Mybatis提供的动态代理类了,但是这个动态代理类帮我们做了什么?怎么就实现了调用接口方法就可以执行xml配置文件里的sql了呢,别着急,让我们来看看Mybatis提供的这个动态代理类,确实到目前为止接口和sql还没有关联,但是仔细一看我们已经为关联做好准备了。methodCache属性具有存储Method和MapperMethod对应关系的功能,而MapperMethod中包含Configuration对象,而Configuration对象包含xml配置文件的所有信息,当然也就包含sql的信息,这就为我们建立接口方法和sql的关联关系做好了准备。再接下来就是具体的增删改查功能了,这是mybatis最最核心的功能,也是最复杂的功能,我们会多篇博文来介绍增删改查
在介绍增删改查之前,我们先来看一下为什么通过接口查询可以设置多个参数,而通过普通的方法查询不行,通过普通方法查询时Mybatis是不知道我们到底要传进去多少个参数,每个参数之间的关系是什么样的,所以Mybatis规定只能有一个参数,如果我们想传多个参数有一般是有两种方式,将多个参数封装成一个对象或者将多个参数放到一个map中,这对于开发者来说是很不方便的,既然mybatis规定参数只能是一个为什么通过接口查询就可以是多个参数呢,因为mybatis在内部将我们传进去的多个参数转成了一个map省去我们自己转换的这个步骤,下面看看是如何转换的,jdk的动态代理会将方法的参数转成一个Object[]数组,定义多个参数时必须使用@Param注解Mybatis才可以识别,像下面这样声明多个参数
public List<Message> queryMessageList(@Param(value = "command") String command,@Param(value="description")String description);
在调用增删改查方法时,mybatis通过反射可以知道mybatis有多个参数,如果没有参数下面方法直接返回null,如果有一个参数就返回args[0],如果有多个参数根据@Param的顺序依次放到map中,转换成map之后普通方法执行数据库操作和通过接口操作数据库就没有区别了
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
解析完配置文件之后,配置文件中的所有信息就存储到了Configuration对象中了,在创建SqlSessionFactory对象时将Configuration做为SqlSessionFactory对象的一个属性,通过调用SqlSessionFactory的openSession(...),openSession()有很多重载方法,创建SqlSession对象只能通过openSession()和这些重载方法
@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }下面的代码是实际创建SqlSession的地方,在这里首先要获得解析XML文件时创建的TransactionFacroty对象,其次根据TransactionFactory创建事务对象Transaction,然后通过execType创建Executor,execType的类型决定了创建的Executor对象的类型
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); final Executor executor = configuration.newExecutor(tx, execType); 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(); } }
executorType有三种类型,分别是BATCH,REUSE,SIMPLE,默认的是SIMPLE类型,这三种类型分别对应着三种executor,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor就是简单的执行sql操作,除了这三种类型之外Mybatis还支持CacheExecutor,这就是Mybatis内建的缓存机制,在SqlSession级别提供对SQL查询结果的缓存,即:如果你调用了相同的select查询,Mybatis会将放在缓存的结果返回,而不会去再查询数据库,后面具体分析查询过程时会分析Mybatis是如何实现缓存的,在Mybatis中真正负责执行数据库操作的就是这些Executor
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 { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }最后executor对象是可以被插件拦截的,关于插件后面会有专门的博文进行分析
通过代理获得Mapper
Mybatis建议通过mapper对象访问mybatis,使用mapper和使用普通的方式的区别主要有以下几点
public int getUserInfo(long userId){ return getSqlSession().selectOne("net.klq.inter.po.TbNsBssUserInfoCn.selectUserInfoCn", userId); }
1、普通的方式在dao层查询的id和namespace是手写的,和xml中的配置文件有可能会对不上,实际工作中经常会碰到这种状况,而使用mapper的时候每个接口在哪个包下是固定的不会随意改变,省去了我们写包名时候容易导致的错误
2、普通的方式入参只能有一个,如果sql需要多个参数,我们需要先把多个参数封装成一个对象或者是放到map中,而使用mapper的时候可以使用任意多个参数
知道了通过Mapper编程的好处之后,思考一个问题mapper是一个接口,没有实现类怎么可以调用该接口的方法呢?答案就是动态代理,使用sqlSession的getMapper(...)方法,在调用该方法的时候传我们需要的Mapper接口的class对象进去,mybatis就可以为我们创建该对象的实现类了,具体是怎么创建的呢,一起来看看
MessageMapperDao messageMapper = sqlSession.getMapper(MessageMapperDao.class);Mybatis会去knownMappers中查找是否为该接口创建了一个代理工厂,在Mybatis中使用MapperProxyFactory作为代理工厂,找到代理工厂后会通过jdk的动态代理功能为我们创建一个动态代理对象然后返回,在应用中用接口来引用这个动态代理对象,接下来我们所有的增删改查都是通过这个动态代理对象进行的,Mybatis使用MapperProxy作为动态代理的对象,而且Mybatis在创建代理对象时将sqlSession的信息给了MapperProxy,这就
@SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 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); } }
到目前为止我们已经找到了Mybatis提供的动态代理类了,但是这个动态代理类帮我们做了什么?怎么就实现了调用接口方法就可以执行xml配置文件里的sql了呢,别着急,让我们来看看Mybatis提供的这个动态代理类,确实到目前为止接口和sql还没有关联,但是仔细一看我们已经为关联做好准备了。methodCache属性具有存储Method和MapperMethod对应关系的功能,而MapperMethod中包含Configuration对象,而Configuration对象包含xml配置文件的所有信息,当然也就包含sql的信息,这就为我们建立接口方法和sql的关联关系做好了准备。再接下来就是具体的增删改查功能了,这是mybatis最最核心的功能,也是最复杂的功能,我们会多篇博文来介绍增删改查
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override 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 t) { throw ExceptionUtil.unwrapThrowable(t); } } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
在介绍增删改查之前,我们先来看一下为什么通过接口查询可以设置多个参数,而通过普通的方法查询不行,通过普通方法查询时Mybatis是不知道我们到底要传进去多少个参数,每个参数之间的关系是什么样的,所以Mybatis规定只能有一个参数,如果我们想传多个参数有一般是有两种方式,将多个参数封装成一个对象或者将多个参数放到一个map中,这对于开发者来说是很不方便的,既然mybatis规定参数只能是一个为什么通过接口查询就可以是多个参数呢,因为mybatis在内部将我们传进去的多个参数转成了一个map省去我们自己转换的这个步骤,下面看看是如何转换的,jdk的动态代理会将方法的参数转成一个Object[]数组,定义多个参数时必须使用@Param注解Mybatis才可以识别,像下面这样声明多个参数
public List<Message> queryMessageList(@Param(value = "command") String command,@Param(value="description")String description);
在调用增删改查方法时,mybatis通过反射可以知道mybatis有多个参数,如果没有参数下面方法直接返回null,如果有一个参数就返回args[0],如果有多个参数根据@Param的顺序依次放到map中,转换成map之后普通方法执行数据库操作和通过接口操作数据库就没有区别了
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next().intValue()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
相关文章推荐
- Mybatis传递多个参数的解决办法(三种)
- 获取Java的MyBatis框架项目中的SqlSession的方法
- 深入浅析mybatis oracle BLOB类型字段保存与读取
- MyBatis MapperProvider MessageFormat拼接批量SQL语句执行报错的原因分析及解决办法
- 详解Java的MyBatis框架和Spring框架的整合运用
- Java的MyBatis框架项目搭建与hellow world示例
- SpringMVC整合mybatis实例代码
- oracle+mybatis 使用动态Sql当插入字段不确定的情况下实现批量insert
- MyBatis学习笔记(二)之关联关系
- 浅析Mybatis 在CS程序中的应用
- Java Mybatis框架入门基础教程
- Windows下Java+MyBatis框架+MySQL的开发环境搭建教程
- Mybatis与Ibatis的区别
- MyBatis学习教程(二)―如何使用MyBatis对users表执行CRUD操作
- Java+MyBatis+MySQL开发环境搭建流程详解
- MyBatis学习教程(四)-如何快速解决字段名与实体类属性名不相同的冲突问题
- Java环境中MyBatis与Spring或Spring MVC框架的集成方法
- Java简单实现SpringMVC+MyBatis分页插件
- 详解Java的MyBatis框架与Spring框架整合中的映射器注入