mybatis查询语句的背后
转载请注明出处。。。
一、前言
在先了解mybatis查询之前,先大致了解下以下代码的为查询做了哪些铺垫,在这里我们要事先了解,myabtis会默认使用DefaultSqlSessionFactory作为sqlSessionFactory的实现类,而sqlSession的默认实现类为DefaultSqlSession
1 public static SqlSessionFactory getSessionFactory() throws IOException { 2 Reader reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml"); 3 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 4 return builder.build(reader); 5 }
获取mybatis的配置文件流,交给sqlSessionFactoryBuilder进行解析,在这里只会涉及到一部分,具体,请大家移步mybatis源码进行分析
解析大致步骤(以下说的配置文件,是mybatis配置数据库连接信息的那个配置文件,不是mapper.xml文件)
解析配置文件的核心类在XMLConfigBuilder类中,
代码如下
1 public Configuration parse() { 2 if (parsed) { 3 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 4 } 5 parsed = true; 6 parseConfiguration(parser.evalNode("/configuration")); 7 return configuration; 8 } 9 10 private void parseConfiguration(XNode root) { 11 try { 12 // 解析properties节点信息 13 propertiesElement(root.evalNode("properties")); 14 // 解析settings节点配置信息,其中二级缓存的总开关就是这里配置,当然mybatis默认是开启的,详细见Configuration类中的cacheEnabled属性 15 Properties settings = settingsAsProperties(root.evalNode("settings")); 16 loadCustomVfs(settings); 17 loadCustomLogImpl(settings); 18 // 解析别名 19 typeAliasesElement(root.evalNode("typeAliases")); 20 // 解析插件 21 pluginElement(root.evalNode("plugins")); 22 // 这个节点一般不进行配置,myabtis也提供了一个默认实现类DefaultObjectFactory,除非自定义对象工厂实现,才需配置 23 objectFactoryElement(root.evalNode("objectFactory")); 24 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 25 reflectorFactoryElement(root.evalNode("reflectorFactory")); 26 settingsElement(settings); 27 // read it after objectFactory and objectWrapperFactory issue #631 28 environmentsElement(root.evalNode("environments")); 29 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 30 // 处理java类型和数据库类型的转换,mybatis提供了许多默认实现,详细见TypeHandlerRegistry类,如果需自定义,可在此节点中进行配置 31 typeHandlerElement(root.evalNode("typeHandlers")); 32 // 这也是一个核心的配置,mapperElement方法会对mapper.xml文件内容进行一个解析 33 mapperElement(root.evalNode("mappers")); 34 } catch (Exception e) { 35 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 36 } 37 }
解析mapper.xml文件 的类XMLMapperBuilder,
1 public void parse() { 2 // 也就是检测配置文件配置的mapper节点有没有加载到configuration类中,防止重复加载 3 if (!configuration.isResourceLoaded(resource)) { 4 configurationElement(parser.evalNode("/mapper")); 5 configuration.addLoadedResource(resource); 6 // 这个是绑定,mapper接口的,当处理成功,在configuration类中的mapper注册器中,会添加一个mapper 7 bindMapperForNamespace(); 8 } 9 10 parsePendingResultMaps();// 解析resultMap节点 11 parsePendingCacheRefs(); // 解析缓存节点,如<cache-ref/> 12 parsePendingStatements();// 解析select|update等节点,并封装成mappedStatement类 13 }
其中bindMapperForNamespace()方法的操作会导致以下结果
在configuration类中的MapperRegistry属性中添加一个mapper,结果存储在MapperRegistry类的一个map中,key为mapper的class value为一个代理工厂,负责产生mapper接口代理类。
二、查询操作
当我们使用要使用mybatis进行查询操作,无非大致就是两种方式
1 /** 2 * 通过mapper接口形式查询数据 3 */ 4 @Test 5 public void testSelectByMapper() throws IOException { 6 SqlSession sqlSession = MybatisUtil.getSessionFactory().openSession(); 7 UserMapper mapper = sqlSession.getMapper(UserMapper.class); 8 User user = mapper.selectByPrimaryKey(10); 9 System.out.println(user); 10 sqlSession.close(); 11 } 12 13 /** 14 * 通过mapper接口的全限定名来进行查询 15 * @throws IOException 16 */ 17 @Test 18 public void testSelectByString() throws IOException { 19 SqlSessionFactory sessionFactory = MybatisUtil.getSessionFactory(); 20 SqlSession sqlSession = sessionFactory.openSession(); 21 User user = sqlSession.selectOne("com.mybatis.demo.mybatisdemo.mapper.UserMapper.selectByPrimaryKey",10); 22 System.out.println(user); 23 sqlSession.close(); 24 }
先来看第一种的分析,当我们点击getMapper进去,它会去调用configuration类中getMapper方法,就如上面介绍的解析出mapper节点后,会存储在configuration类中的mapper注册器中,
1 // defaultSqlSession类 2 public <T> T getMapper(Class<T> type) { 3 return configuration.<T>getMapper(type, this); 4 } 5 //configuration类 6 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 7 return mapperRegistry.getMapper(type, sqlSession); 8 } 9 // 最终获取mapper对象的方法,其主要是创建一个mapper代理工厂,我们都知道mybatis的mapper接口是没有实现类的, 10 // 但是我们直接查询是能获取数据,这里起作用的就是代理(采用的是jdk动态代理) 11 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 12 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 13 if (mapperProxyFactory == null) { 14 throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 15 } 16 try { 17 return mapperProxyFactory.newInstance(sqlSession); 18 } catch (Exception e) { 19 throw new BindingException("Error getting mapper instance. Cause: " + e, e); 20 } 21 }
然后最终会经过代理类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方法也是负责返回最终结果的
1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2 try { 3 if (Object.class.equals(method.getDeclaringClass())) { 4 return method.invoke(this, args); 5 } else if (isDefaultMethod(method)) { 6 return invokeDefaultMethod(proxy, method, args); 7 } 8 } catch (Throwable t) { 9 throw ExceptionUtil.unwrapThrowable(t); 10 } 11 // 交给了mpperMethod类去处理 12 final MapperMethod mapperMethod = cachedMapperMethod(method); 13 return mapperMethod.execute(sqlSession, args); 14 }
mapperMethod类中有两个重要属性,也就是它的内部类,
也可以很清楚的了解到SqlCommand是用来存储当前执行方法的信息,如全限定名,还有该方法是属于select|update|delete|insert|flush的哪一种,
对于methodSignature,则是纪录该方法的一些信息,如返回值类型,参数等信息,paramNameResolver处理mapper接口中的参数,下面代码中有一个大致的介绍,以后会做一个详细的介绍,这里只贴下代码,只针对select做介绍
1 public Object execute(SqlSession sqlSession, Object[] args) { 2 Object result; 3 switch (command.getType()) { 4 case INSERT: { 5 Object param = method.convertArgsToSqlCommandParam(args); 6 result = rowCountResult(sqlSession.insert(command.getName(), param)); 7 break; 8 } 9 case UPDATE: { 10 Object param = method.convertArgsToSqlCommandParam(args); 11 result = rowCountResult(sqlSession.update(command.getName(), param)); 12 break; 13 } 14 case DELETE: { 15 Object param = method.convertArgsToSqlCommandParam(args); 16 result = rowCountResult(sqlSession.delete(command.getName(), param)); 17 break; 18 } 19 case SELECT: 20 if (method.returnsVoid() && method.hasResultHandler()) {// 返回值为void类型,但是有ResultHandler参数,并且只能有一个,不然会报错 21 executeWithResultHandler(sqlSession, args); 22 result = null; 23 } else if (method.returnsMany()) {// 处理返回值类型为集合类型或者数组类型 24 result = executeForMany(sqlSession, args); 25 } else if (method.returnsMap()) {//处理返回值类型为Map类型 26 result = executeForMap(sqlSession, args); 27 } else if (method.returnsCursor()) {//返回值是否为cursor类型 28 result = executeForCursor(sqlSession, args); 29 } else {//其他类型 30 Object param = method.convertArgsToSqlCommandParam(args); 31 result = sqlSession.selectOne(command.getName(), param); 32 if (method.returnsOptional() && 33 (result == null || !method.getReturnType().equals(result.getClass()))) { 34 result = Optional.ofNullable(result); 35 } 36 } 37 break; 38 case FLUSH: 39 result = sqlSession.flushStatements(); 40 break; 41 default: 42 throw new BindingException("Unknown execution method for: " + command.getName()); 43 } 44 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { 45 throw new BindingException("Mapper method '" + command.getName() 46 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); 47 } 48 return result; 49 }
这里只介绍select部分中常用返回多个实例对象的情况,也就是返回值为集合类型。
1 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { 2 List<E> result; 3 // 将mapper接口的参数名称和args整成一个map结构,最后在会将值赋给sql中对应的变量 4 // 在3.5版本中,默认的mapper结构(假如没使用@param注解或者处于jdk1.8版本中在代码编译时加上 -parameters 参数),结构为 5 // param1 -> args[0] param2 -> args[1] 6 // arg0 -> args[0] arg1 -> args[1] mybatis之前有些版本不是arg0 而是0 1 。。数字代替。 7 Object param = method.convertArgsToSqlCommandParam(args); 8 if (method.hasRowBounds()) {// 处理参数中带有rowBounds参数 9 RowBounds rowBounds = method.extractRowBounds(args); 10 result = sqlSession.<E>selectList(command.getName(), param, rowBounds); 11 } else {// 其它情况 12 result = sqlSession.<E>selectList(command.getName(), param); 13 } 14 // issue #510 Collections & arrays support 15 // 说明返回类型不是集合List类型,而是数组类型或其它集合类型。 16 if (!method.getReturnType().isAssignableFrom(result.getClass())) { 17 if (method.getReturnType().isArray()) { 18 return convertToArray(result); 19 } else { 20 return convertToDeclaredCollection(sqlSession.getConfiguration(), result); 21 } 22 } 23 return result; 24 }
从上面知道,最终还是回到了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类中的一个属性决定最后的类型,
1 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 2 executorType = executorType == null ? defaultExecutorType : executorType; 3 executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 4 Executor executor; 5 if (ExecutorType.BATCH == executorType) { 6 executor = new BatchExecutor(this, transaction); 7 } else if (ExecutorType.REUSE == executorType) { 8 executor = new ReuseExecutor(this, transaction); 9 } else { 10 executor = new SimpleExecutor(this, transaction); 11 } 12 // 如果cacheEnabled为true,其实这个属性默认为true的, 13 // 则由CachingExecutor进行包装,也就是常说的装饰设计模式 14 if (cacheEnabled) { 15 executor = new CachingExecutor(executor); 16 } 17 executor = (Executor) interceptorChain.pluginAll(executor); 18 return executor; 19 }
最后回到selectList中来,由此可见,调用了CachingExecutor类中的query方法来执行。
1 @Override 2 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 3 throws SQLException { 4 // 如果不为空,则启用了二级缓存 5 Cache cache = ms.getCache(); 6 if (cache != null) { 7 flushCacheIfRequired(ms); 8 if (ms.isUseCache() && resultHandler == null) { 9 ensureNoOutParams(ms, boundSql); 10 @SuppressWarnings("unchecked") 11 List<E> list = (List<E>) tcm.getObject(cache, key); 12 if (list == null) { 13 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 14 tcm.putObject(cache, key, list); // issue #578 and #116 15 } 16 return list; 17 } 18 } 19 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 20 }
关于二级缓存,相信熟悉的都清楚,要想使用它,需要动两个地方,
一个是mybatis的配置文件,将cacheEnabled设置为true,其实mybatis对这个属性的默认值就是true,所以二级缓存的总开关是打开的。
第二个就是在mpper.xml文件中使用 <cache/> 或<cache-ref/>
这里对缓存不做介绍。
然后调用了BaseExecutor的query方法,这个类起的作用就是对一级缓存进行了操作,最终调用了SimpleExecutor的doQuery方法进行查询。