mybatis查询语句的背后揭秘

网友投稿 267 2023-01-09


mybatis查询语句的背后揭秘

一、前言

在先了解mybatis查询之前,先大致了解下以下代码的为查询做了哪些铺垫,在这里我们要事先了解,myabtis会默认使用DefaultSqlSessionFactory作为sqlSessionFactory的实现类,而sqlSession的默认实现类为DefaultSqlSession

public static SqlSessionFactory getSessionFactory() throws IOException {

Reader reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml");

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

return builder.build(reader);

}

获取mybatis的配置文件流,交给sqlSessionFactoryBuilder进行解析,在这里只会涉及到一部分,具体,请大家移步mybatis源码进行分析

解析大致步骤(以下说的配置文件,是mybatis配置数据库连接信息的那个配置文件,不是mapper.xml文件)

解析配置文件的核心类在XMLConfigBuilder类中,

代码如下

public Configuration parse() {

if (parsed) {

throw new BuilderException("Each XMLConfigBuilder can only be used once.");

}

parsed = true;

parseConfiguration(parser.evalNode("/configuration"));

return configuration;

}

private void parseConfiguration(XNode root) {

try {

// 解析properties节点信息

propertiesElement(root.evalNode("properties"));

// 解析settings节点配置信息,其中二级缓存的总开关就是这里配置,当然mybatis默认是开启的,详细见Configuration类中的cacheEnabled属性

Properties settings = settingsAsProperties(root.evalNode("settings"));

loadCustomVfs(settings);

loadCustomLogImpl(settings);

// 解析别名

typeAliasesElement(root.evalNode("typeAliases"));

// 解析插件

pluginElement(root.evalNode("plugins"));

// 这个节点一般不进行配置,myabtis也提供了一个默认实现类DefaultObjectFactory,除非自定义对象工厂实现,才需配置

objectFactoryElement(root.evalNode("objectFactory"));

objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

reflectorFactoryElement(root.evalNode("reflectorFactory"));

settingsElement(settings);

// read it after objectFactory and objectWrapperFactory issue #631

environmentsElement(root.evalNode("environments"));

databaseIdProviderElement(root.evalNode("databaseIdProvider"));

// 处理java类型和数据库类型的转换,mybatis提供了许多默认实现,详细见TypeHandlerRegistry类,如果需自定义,可在此节点中进行配置

typeHandlerElement(root.evalNode("typeHandlers"));

// 这也是一个核心的配置,mapperElement方法会对mapper.xml文件内容进行一个解析

mapperElement(root.evalNode("mappers"));

} catch (Exception e) {

throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

}

}

解析mapper.xml文件 的类XMLMapperBuilder,

public void parse() {

// 也就是检测配置文件配置的mapper节点有没有加载到configuration类中,防止重复加载

if (!configuration.isResourceLoaded(resource)) {

configurationElement(parser.evalNode("/mapper"));

configuration.addLoadedResource(resource);

// 这个是绑定,mapper接口的,当处理成功,在configuration类中的mapper注册器中,会添加一个mapper

bindMapperForNamespace();

}

parsePendingResultMaps();// 解析resultMap节点

parsePendingCacheRefs(); // 解析缓存节点,如

parsePendingStatements();// 解析select|update等节点,并封装成mappedStatement类

}

其中bindMapperForNamespace()方法的操作会导致以下结果

在configuration类中的MapperRegistry属性中添加一个mapper,结果存储在MapperRegistry类的一个map中,key为mapper的class value为一个代理工厂,负责产生mapper接口代理类。

二、查询操作

当我们使用要使用mybatis进行查询操作,无非大致就是两种方式

/**

* 通过mapper接口形式查询数据

*/

@Test

public void testSelectByMapper() throws IOException {

SqlSession sqlSession = MybatisUtil.getSessionFactory().openSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

User user = mapper.selectByPrimaryKey(10);

System.out.println(user);

sqlSession.close();

}

/**

* 通过mapper接口的全限定名来进行查询

* @throws IOException

*/

@Test

public void testSelectByString() throws IOException {

SqlSessionFactory sessionFactory = MybatisUtil.getSessionFactory();

SqlSession sqlSession = sessionFactory.openSession();

User user = sqlSession.selectOne("com.mybatis.demo.mybatisdemo.mapper.UserMapper.selectByPrimaryKey",10);

System.out.println(user);

sqlSession.close();

}

先来看第一种的分析,当我们点击getMapper进去,它会去调用configuration类中getMapper方法,就如上面介绍的解析出mapper节点后,会存储在configuration类中的mapper注册器中,

// defaultSqlSession类

public T getMapper(Class type) {

return configuration.getMapper(type, this);

}

//configuration类

public T getMapper(Class type, SqlSession sqlSession) {

return mapperRegistry.getMapper(type, sqlSession);

}

// 最终获取mapper对象的方法,其主要是创建一个mapper代理工厂,我们都知道mybatis的mapper接口是没有实现类的,

// 但是我们直接查询是能获取数据,这里起作用的就是代理(采用的是jdk动态代理)

public T getMapper(Class type, SqlSession sqlSession) {

final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);

}

}

然后最终会经过代理类MapperProxy的invoke方法,进行返回结果。在这里为了更好的能理解这个类,举个例子,步骤如下

先创建一个接口,再使用一个类去实现java的jdk代理的核心接口InvocationHandler,

public interface TestMapper {

User findByUserId(Integer id);

}

public class MapperProxyTest implements InvocationHandler {

private Class> target;

public MapperProxyTest(Class> target) {

this.target = target;

}

public Object getProxyInstances(){

return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{target},this);

}

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (Object.class.equals(method.getDeclaringClass())) {

return method.invoke(this, args);

}

User user = new User();

user.setPassword("123");

user.setUsername("李四");

user.setAddress("123");

user.setRegistertime(new Date());

user.setCellphone("1111111");

user.setAge(25);

return user;

}

}

测试类

public class MapperTest {

public static void main(String[] args){

MapperProxyTest proxyTest = new MapperProxyTest(TestMapper.class);

TestMapper testMapper = (TestMapper) proxyTest.getProxyInstances();

System.out.println(testMapper.findByUserId(10));

}

}

执行结果

User{id=null, username='李四', password='123', age=25, address='123', cellphone='1111111', registertime=Sat Mar 09 15:02:16 CST 2019}

由上面例子也可以看出最终结果是在invoke方法内,同理在mybatis中的MapperProxy的invoke方法也是负责返回最终结果的

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

try {

if (Object.class.equals(method.getDeclaringClass())) {

return method.invoke(this, args);

} else if (isDefaultMethod(method)) {

return invokeDefaultMethod(proxy, method, args);

}

} catch (Throwable t) {

throw ExceptionUtil.unwrapThrowable(t);

}

// 交给了mpperMethod类去处理

final MapperMethod mapperMethod = cachedMapperMethod(method);

return mapperMethod.execute(sqlSession, args);

}

mapperMethod类中有两个重要属性,也就是它的内部类,

也可以很清楚的了解到SqlCommand是用来存储当前执行方法的信息,如全限定名,还有该方法是属于select|update|delete|insert|flush的哪一种,

对于methodSignature,则是纪录该方法的一些信息,如返回值类型,参数等信息,paramNameResolver处理mapper接口中的参数,下面代码中有一个大致的介绍,以后会做一个详细的介绍,这里只贴下代码,只针对select做介绍

public Object execute(SqlSession sqlSession, Object[] args) {

Object result;

switch (command.getType()) {

case INSERT: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.insert(command.getName(), param));

break;

}

case UPDATE: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.update(command.getName(), param));

break;

}

case DELETE: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.delete(command.getName(), param));

break;

}

case SELECT:

if (method.returnsVoid() && method.hasResultHandler()) {// 返回值为void类型,但是有ResultHandler参数,并且只能有一个,不然会报错

executeWithResultHandler(sqlSession, args);

result = null;

} else if (method.returnsMany()) {// 处理返回值类型为集合类型或者数组类型

result = executeForMany(sqlSession, args);

} else if (method.returnsMap()) {//处理返回值类型为Map类型

result = executeForMap(sqlSession, args);

} else if (method.returnsCursor()) {//返回值是否为cursor类型

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());

}

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;

}

这里只介绍select部分中常用返回多个实例对象的情况,也就是返回值为集合类型。

private Object executeForMany(SqlSession sqlSession, Object[] args) {

List result;

// 将mapper接口的参数名称和args整成一个map结构,最后在会将值赋给sql中对应的变量

// 在3.5版本中,默认的mapper结构(假如没使用@param注解或者处于jdk1.8版本中在代码编译时加上 -parameters 参数),结构为

// param1 -> args[0] param2 -> args[1]

// arg0 -> args[0] arg1 -> args[1] mybatis之前有些版本不是arg0 而是0 1 。。数字代替。

Object param http://= method.convertArgsToSqlCommandParam(args);

if (method.hasRowBounds()) {// 处理参数中带有rowBounds参数

RowBounds rowBounds = method.extractRowBounds(args);

result = sqlSession.selectList(command.getName(), param, rowBounds);

} else {// 其它情况

result = sqlSession.selectList(command.getName(), param);

}

// issue #510 Collections & arrays support

// 说明返回类型不是集合List类型,而是数组类型或其它集合类型。

if (!method.getReturnType().isAssignableFrom(result.getClass())) {

if (method.getReturnType().isArray()) {

return convertToArray(result);

} else {

return convertToDeclaredCollection(sqlSession.getConfiguration(), result);

}

}

return result;

}

从上面知道,最终还是回到了sqlSession里面,

@Override

public List selectList(String statement, Object parameter, RowBounds rowBounds) {

try {

MappedStatement ms = configuration.getMappedStatement(statement);

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();

}

}

MappedStatement存储的其实就是对每一个select|update|delete|insert 标签的解析结果

关于MappedStatement是怎么解析得来的,又是怎么存储在Configuration中,可沿着以下路线进行查看

SqlSessionFactoryBuilder  ---> build方法

XMLConfigBuilder  ---->  parse、parseConfiguration、mapperElement方法

XMLMapperBuilder   ----> parse、parsePendingStatements、parseStatementNode

MapperBuilderAssistant    ----> addMappedStatement

这里不做过多介绍,详情见源码

在selectList中executor的默认实现类是,SimpleExecutor,不过它还由Configuration类中的一个属性决定最后的类型,

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);

}

// 如果cacheEnabled为true,其实这个属性默认为true的,

// 则由CachingExecutor进行包装,也就是常说的装饰设计模式

if (cacheEnabled) {

executor = new CachingExecutor(executor);

}

executor = (Executor) interceptorChain.pluginAll(executor);

return executor;

}

最后回到selectList中来,由此可见,调用了CachingExecutor类中的query方法来执行。

@Override

public List query(MappedStatement ms, Object parameterObjhttp://ect, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)

throws SQLException {

// 如果不为空,则启用了二级缓存

Cache cache = ms.getCache();

if (cache != null) {

flushCacheIfRequired(ms);

if (ms.isUseCache() && resultHandler == null) {

ensureNoOutParams(ms, boundSql);

@SuppressWarnings("unchecked")

List list = (List) 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);

}

关于二级缓存,相信熟悉的都清楚,要想使用它,需要动两个地方,

一个是mybatis的配置文件,将cacheEnabled设置为true,其实mybatis对这个属性的默认值就是true,所以二级缓存的总开关是打开的。

第二个就是在mpper.xml文件中使用   或

这里对缓存不做介绍。

然后调用了BaseExecutor的query方法,这个类起的作用就是对一级缓存进行了操作,最终调用了SimpleExecutor的doQuery方法进行查询。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:自动化接口测试框架 一键(自动化接口测试框架 一键安装)
下一篇:Spring boot项目redisTemplate实现轻量级消息队列的方法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~