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(); // 解析缓存节点,如<cache-ref/>
 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> T getMapper(Class<T> type) {
 return configuration.<T>getMapper(type, this);
 }
//configuration类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 return mapperRegistry.getMapper(type, sqlSession);
 }
// 最终获取mapper对象的方法,其主要是创建一个mapper代理工厂,我们都知道mybatis的mapper接口是没有实现类的,
// 但是我们直接查询是能获取数据,这里起作用的就是代理(采用的是jdk动态代理)
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);
 }
 }

然后最终会经过代理类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 <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
 List<E> 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 = method.convertArgsToSqlCommandParam(args);
 if (method.hasRowBounds()) {// 处理参数中带有rowBounds参数
  RowBounds rowBounds = method.extractRowBounds(args);
  result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
 } else {// 其它情况
  result = sqlSession.<E>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 <E> List<E> 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 <E> List<E> query(MappedStatement ms, Object parameterObject, 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<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);
 }

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

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

第二个就是在mpper.xml文件中使用 <cache/>  或<cache-ref/>

这里对缓存不做介绍。

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

总结

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

(0)

相关推荐

  • Mybatis查询语句结果集的总结大全

    简单查询-resultType 数据准备 字段 注释 SNO 学号 SNAME 学生名字 SSEX 性别 SBIRITHDAY 生日 CLASS 班级 <!--建表语句:--> create table TEST.STUDENT ( SNO varchar(3) not null, SNAME varchar(4) not null, SSEX varchar(2) not null, SBIRTHDAY datetime null, CLASS varchar(5) null ) <!

  • Mybatis多表关联查询的实现(DEMO)

    概要 本节要实现的是多表关联查询的简单demo.场景是根据id查询某商品分类信息,并展示该分类下的商品列表. 一.Mysql测试数据 新建表Category(商品分类)和Product(商品),并插入几条测试数据. create table Category ( Id int not null auto_increment, Name varchar(80) null, constraint pk_category primary key (Id) ); INSERT INTO category

  • mybatis查询语句揭秘之封装数据

    一.前言 继上一篇mybatis查询语句的背后,这一篇主要围绕着mybatis查询的后期操作,即跟数据库交互的时候.由于本人也是一边学习源码一边记录,内容难免有错误或不足之处,还望诸位指正,本文只可当参考作用.谨记! 二.分析 继上一篇博文的查询例子,mybatis在最后的查询最终会走SimpleExecutor类的doQuery方法, @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter

  • Mybatis模糊查询和动态sql语句的用法

    Mybatis 模糊查询和动态sql语句 模糊查询 对数据库最常用的操作就是查询了,但是如何使用Mybatis进行模糊查询呢?下面先看一个简单的模糊查询 <select id="select01" resultMap="BasicResultMap"> SELECT * FROM oa_employee WHERE emp_name LIKE #{asd} </select> 这是一条伪模糊查询, 因为没有实现真正的模糊 "%&qu

  • MyBatis中的模糊查询语句

    其实就只有一条sql语句 <select id = "search" resultType = "material"> select material_id,material_num,material_name,material_type,material_model,id from material where material_name like '%${value}%' or material_num like '%${value}%' </

  • mybatis查询语句揭秘之参数解析

    一.前言 通过前面我们也知道,通过getMapper方式来进行查询,最后会通过mapperMehod类,对接口中传来的参数也会在这个类里面进行一个解析,随后就传到对应位置,与sql里面的参数进行一个匹配,最后获取结果.对于mybatis通常传参(这里忽略掉Rowbounds和ResultHandler两种类型)有几种方式. 1.javabean类型参数 2.非javabean类型参数 注意,本文是基于mybatis3.5.0版本进行分析. 1.参数的存储 2.对sql语句中参数的赋值 下面将围绕

  • Mybatis实现增删改查及分页查询的方法

    MyBatis的前身就是iBatis.是一个数据持久层(ORM)框架. MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持 久层框架.MyBatis消除了几乎所有的JDBC 代码和参数的手工 设置以及结果集的检索.MyBatis使用简单的XML或注解用于 配置和原始映射,将接口和Java 的POJOs(Plan Old Java Objects,普通的Java 对象)映射成数据库中的记录.每个 MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个 SqlS

  • 详解MyBatis直接执行SQL查询及数据批量插入

    一.直接执行SQL查询: 1.mappers文件节选 <resultMap id="AcModelResultMap" type="com.izumi.InstanceModel"> <result column="instanceid" property="instanceID" jdbcType="VARCHAR" /> <result column="insta

  • Mybatis传递多个参数进行SQL查询的用法

    PS:ibatis3如何传递多个参数有两个方法:一种是使用java.Map,另一种是使用JavaBean. 当只向xxxMapper.xml文件中传递一个参数时,可以简单的用"_parameter"来接收xxxMapper.java传递进来的参数,并代入查询,比如说这样: (1)xxxMapper.java文件中这样定义: List<String> selectAllAirportCode(Boolean mapping); (2)这时在对应的xxxMapper.xml文件

  • Mybatis查不到数据查询返回Null问题

    mybatis突然查不到数据,查询返回的都是Null,但是 select count(*) from xxx查询数量,返回却是正常的. Preparing: SELECT id,a9004,a9005,a9015 FROM a90 where a9010 = ? ORDER BY id LIMIT 1 [DEBUG] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:139):http-bio-8080

随机推荐