个人随笔
目录
二、一个简单的例子来探寻MyBatis执行源码的主脉络
2021-06-30 19:13:45

MyBatis的执行流程看源码的话相对来说还是比较简单的,只需要跟着源码走,用点心,就应该OK,这里按代码执行逻辑大概走一遍执行流程。

一、环境搭建

可以参考一、快速搭建MyBatis开发环境(配置版+注解版)来搭建环境

二、源码分析

看源码我们不能一头雾水的跟着工具调试下去,得带着目的走,我们得找到看源码的目的,我的目的是了解框架的执行逻辑,是怎么做到的?比如MyBatis的运行,先看下MyBatis是怎么运行的。

1、MyBatis的使用

首先,我们引入依赖
pom.xml

  1. <!-- mybatis框架依赖 -->
  2. <dependency>
  3. <groupId>org.mybatis</groupId>
  4. <artifactId>mybatis</artifactId>
  5. <version>3.4.6</version>
  6. </dependency>
  7. <!-- mysql数据库链接依赖 -->
  8. <dependency>
  9. <groupId>mysql</groupId>
  10. <artifactId>mysql-connector-java</artifactId>
  11. <version>5.1.45</version>
  12. </dependency>

然后,配置文件配置数据库连接信息
SqlMapConfig.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <!-- mybatis的主配置文件 -->
  6. <configuration>
  7. <!-- 配置环境 -->
  8. <environments default="mysql">
  9. <!-- 配置mysql的环境-->
  10. <environment id="mysql">
  11. <!-- 配置事务的类型-->
  12. <transactionManager type="JDBC"></transactionManager>
  13. <!-- 配置数据源(连接池) -->
  14. <dataSource type="POOLED">
  15. <!-- 配置连接数据库的4个基本信息 -->
  16. <property name="driver" value="com.mysql.jdbc.Driver"/>
  17. <property name="url" value="jdbc:mysql://******:3309/suibibk"/>
  18. <property name="username" value="root"/>
  19. <property name="password" value="*******"/>
  20. </dataSource>
  21. </environment>
  22. </environments>
  23. <mappers>
  24. <!-- 不一定非得叫UserMapper.xml -->
  25. <mapper resource="mybatis/UserMapper.xml"/>
  26. </mappers>
  27. </configuration>

然后我们写一个Mapper
UserMapper.java

  1. public interface UserMapper {
  2. public List<User> findAllUser();
  3. }

再写一个SQL配置文件
UserMapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.suibibk.mybatis.UserMapper">
  6. <!--配置查询所有-->
  7. <select id="findAllUser" resultType="com.suibibk.mybatis.User">
  8. select * from user
  9. </select>
  10. </mapper>

然后就可以执行了

  1. public class Test {
  2. public static void main(String[] args) throws IOException {
  3. //1、读取配置文件
  4. InputStream in = Resources.getResourceAsStream("mybatis/SqlMapConfig.xml");
  5. //2、创建SqlSessionFactory工厂
  6. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  7. SqlSessionFactory factory = builder.build(in);
  8. //3、使用工厂生产SqlSession对象
  9. SqlSession sqlSession = factory.openSession();
  10. //4、使用SqlSession创建Dao接口的代理对象
  11. UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  12. //5、使用代理对象执行方法
  13. List<User> user = userMapper.findAllUser();
  14. System.out.println("user:"+user.toString());
  15. //6、释放资源
  16. sqlSession.close();
  17. in.close();
  18. }
  19. }

这个可比用原始的JDBC方便了,我们只需要在UserMapper.xml文件加上要执行的sql脚本和UserMapper.java上加上执行方法就可以了。

所以我好奇的问题是,MyBatis是怎么做到的呢?为啥UserMapper.java和UserMapper.xml可以这样关联?什么时候连接数据库?什么时候去执行sql?

带着这些疑问,我们来跟踪下源码!

2、啥时候解析配置文件?

很多框架第一步就是初始化配置文件,然后把配置文件相关的信息加载到一个配置类里,我们来看看MyBatis是不是也这样做的。

先看第一步读取配置文件

  1. //1、读取配置文件
  2. InputStream in = Resources.getResourceAsStream("mybatis/SqlMapConfig.xml");

很明显,就是把我们的配置文件读成以恶搞输入流,这个没啥好看的。

第二步骤创建SessionFactory工厂

  1. //2、创建SqlSessionFactory工厂
  2. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  3. SqlSessionFactory factory = builder.build(in);

很明显就是根据第一步的输入流中的配置文件构造对象,我们跟进build方法去,我这只摘录主要方法
SqlSessionFactoryBuilder.java

  1. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  2. try {
  3. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  4. return build(parser.parse());
  5. } catch (Exception e) {
  6. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  7. } finally {
  8. ErrorContext.instance().reset();
  9. try {
  10. inputStream.close();
  11. } catch (IOException e) {
  12. // Intentionally ignore. Prefer previous error.
  13. }
  14. }
  15. }

上面开始是生成文件解析器,这个可以不用看,主要是看用文件解析器解析文件的逻辑即可。

  1. parser.parse()

XMLConfigBuilder.java

  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. }

很明显我们要看解析configuration节点(SqlMapConfig.xml)。
继续走

  1. private void parseConfiguration(XNode root) {
  2. try {
  3. //issue #117 read properties first
  4. propertiesElement(root.evalNode("properties"));
  5. Properties settings = settingsAsProperties(root.evalNode("settings"));
  6. loadCustomVfs(settings);
  7. typeAliasesElement(root.evalNode("typeAliases"));
  8. pluginElement(root.evalNode("plugins"));
  9. objectFactoryElement(root.evalNode("objectFactory"));
  10. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  11. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  12. settingsElement(settings);
  13. // read it after objectFactory and objectWrapperFactory issue #631
  14. environmentsElement(root.evalNode("environments"));
  15. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  16. typeHandlerElement(root.evalNode("typeHandlers"));
  17. mapperElement(root.evalNode("mappers"));
  18. } catch (Exception e) {
  19. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  20. }
  21. }

我们的SqlMapConfig.xml中,主要的是environments也就是配置数据库相关链接的节点和mappers配置sql脚本的节点。

这里先看怎么解析environments。

  1. private void environmentsElement(XNode context) throws Exception {
  2. if (context != null) {
  3. if (environment == null) {
  4. environment = context.getStringAttribute("default");
  5. }
  6. for (XNode child : context.getChildren()) {
  7. String id = child.getStringAttribute("id");
  8. if (isSpecifiedEnvironment(id)) {
  9. TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
  10. DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
  11. DataSource dataSource = dsFactory.getDataSource();
  12. Environment.Builder environmentBuilder = new Environment.Builder(id)
  13. .transactionFactory(txFactory)
  14. .dataSource(dataSource);
  15. configuration.setEnvironment(environmentBuilder.build());
  16. }
  17. }
  18. }
  19. }

我们可以看到生成了一个DataSource,然后构造成一个环境变量放到了configuration中。

这,果然框架都万变不离其中,Spring也是,最终都是把配置文件解析的内容放到配置类里,然后给后面用。我们先略微看下configuration

  1. public class Configuration {
  2. protected Environment environment;
  3. protected boolean safeRowBoundsEnabled;
  4. ...
  5. protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  6. protected final InterceptorChain interceptorChain = new InterceptorChain();
  7. protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  8. protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  9. protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  10. protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  11. protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  12. protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  13. protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  14. protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
  15. ...
  16. }

看到没,配置类超级多内容,但是可以肯定的是,解析的配置文件肯定放这里的。

上面把数据库链接的相关信息都放到了Configuration.environment中,我们再来看看看mappers配置放到哪里?

跟踪mappers节点的解析方法

  1. mapperElement(root.evalNode("mappers"));
  1. private void mapperElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. if ("package".equals(child.getName())) {
  5. String mapperPackage = child.getStringAttribute("name");
  6. configuration.addMappers(mapperPackage);
  7. } else {
  8. String resource = child.getStringAttribute("resource");
  9. String url = child.getStringAttribute("url");
  10. String mapperClass = child.getStringAttribute("class");
  11. if (resource != null && url == null && mapperClass == null) {
  12. ErrorContext.instance().resource(resource);
  13. InputStream inputStream = Resources.getResourceAsStream(resource);
  14. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  15. mapperParser.parse();
  16. } else if (resource == null && url != null && mapperClass == null) {
  17. ErrorContext.instance().resource(url);
  18. InputStream inputStream = Resources.getUrlAsStream(url);
  19. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  20. mapperParser.parse();
  21. } else if (resource == null && url == null && mapperClass != null) {
  22. Class<?> mapperInterface = Resources.classForName(mapperClass);
  23. configuration.addMapper(mapperInterface);
  24. } else {
  25. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  26. }
  27. }
  28. }
  29. }
  30. }

这里分了几个类,我们的配置文件中

  1. <mapper resource="mybatis/UserMapper.xml"/>

用的是resource,所以直接看代码

  1. if (resource != null && url == null && mapperClass == null) {
  2. ErrorContext.instance().resource(resource);
  3. InputStream inputStream = Resources.getResourceAsStream(resource);
  4. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  5. mapperParser.parse();
  6. }
  1. public void parse() {
  2. if (!configuration.isResourceLoaded(resource)) {
  3. configurationElement(parser.evalNode("/mapper"));
  4. configuration.addLoadedResource(resource);
  5. bindMapperForNamespace();
  6. }
  7. parsePendingResultMaps();
  8. parsePendingCacheRefs();
  9. parsePendingStatements();
  10. }

这里我们知道Resource配置的是mybatis/UserMapper.xml路径,肯定是存在的,所以走

  1. configurationElement(parser.evalNode("/mapper"));
  1. private void configurationElement(XNode context) {
  2. try {
  3. String namespace = context.getStringAttribute("namespace");
  4. if (namespace == null || namespace.equals("")) {
  5. throw new BuilderException("Mapper's namespace cannot be empty");
  6. }
  7. builderAssistant.setCurrentNamespace(namespace);
  8. cacheRefElement(context.evalNode("cache-ref"));
  9. cacheElement(context.evalNode("cache"));
  10. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  11. resultMapElements(context.evalNodes("/mapper/resultMap"));
  12. sqlElement(context.evalNodes("/mapper/sql"));
  13. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  14. } catch (Exception e) {
  15. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  16. }
  17. }

这里我的配置文件中的是

  1. <select id="findAllUser" resultType="com.suibibk.mybatis.User">
  2. select * from user
  3. </select>

所以我们看

  1. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  1. private void buildStatementFromContext(List<XNode> list) {
  2. if (configuration.getDatabaseId() != null) {
  3. buildStatementFromContext(list, configuration.getDatabaseId());
  4. }
  5. buildStatementFromContext(list, null);
  6. }
  1. private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  2. for (XNode context : list) {
  3. final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
  4. try {
  5. statementParser.parseStatementNode();
  6. } catch (IncompleteElementException e) {
  7. configuration.addIncompleteStatement(statementParser);
  8. }
  9. }
  10. }

继续
XMLStatementBuilder.java

  1. public void parseStatementNode() {
  2. String id = context.getStringAttribute("id");
  3. String databaseId = context.getStringAttribute("databaseId");
  4. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  5. return;
  6. }
  7. Integer fetchSize = context.getIntAttribute("fetchSize");
  8. Integer timeout = context.getIntAttribute("timeout");
  9. String parameterMap = context.getStringAttribute("parameterMap");
  10. String parameterType = context.getStringAttribute("parameterType");
  11. Class<?> parameterTypeClass = resolveClass(parameterType);
  12. String resultMap = context.getStringAttribute("resultMap");
  13. String resultType = context.getStringAttribute("resultType");
  14. String lang = context.getStringAttribute("lang");
  15. LanguageDriver langDriver = getLanguageDriver(lang);
  16. Class<?> resultTypeClass = resolveClass(resultType);
  17. String resultSetType = context.getStringAttribute("resultSetType");
  18. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  19. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  20. String nodeName = context.getNode().getNodeName();
  21. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  22. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  23. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  24. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  25. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  26. // Include Fragments before parsing
  27. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  28. includeParser.applyIncludes(context.getNode());
  29. // Parse selectKey after includes and remove them.
  30. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  31. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  32. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  33. String resultSets = context.getStringAttribute("resultSets");
  34. String keyProperty = context.getStringAttribute("keyProperty");
  35. String keyColumn = context.getStringAttribute("keyColumn");
  36. KeyGenerator keyGenerator;
  37. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  38. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  39. if (configuration.hasKeyGenerator(keyStatementId)) {
  40. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  41. } else {
  42. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  43. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  44. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  45. }
  46. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  47. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  48. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  49. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  50. }

这里我们可以看到,全是解析select节点的,比如获取id

  1. String id = context.getStringAttribute("id");

参数,返回类型

  1. String parameterType = context.getStringAttribute("parameterType");
  2. Class<?> parameterTypeClass = resolveClass(parameterType);
  3. String resultMap = context.getStringAttribute("resultMap");
  4. String resultType = context.getStringAttribute("resultType");

解析sql

  1. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  2. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

总之,就是把select节点彻底解析了。

解析到最后

  1. public MappedStatement addMappedStatement(
  2. String id,
  3. SqlSource sqlSource,
  4. StatementType statementType,
  5. SqlCommandType sqlCommandType,
  6. Integer fetchSize,
  7. Integer timeout,
  8. String parameterMap,
  9. Class<?> parameterType,
  10. String resultMap,
  11. Class<?> resultType,
  12. ResultSetType resultSetType,
  13. boolean flushCache,
  14. boolean useCache,
  15. boolean resultOrdered,
  16. KeyGenerator keyGenerator,
  17. String keyProperty,
  18. String keyColumn,
  19. String databaseId,
  20. LanguageDriver lang,
  21. String resultSets) {
  22. if (unresolvedCacheRef) {
  23. throw new IncompleteElementException("Cache-ref not yet resolved");
  24. }
  25. id = applyCurrentNamespace(id, false);
  26. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  27. MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
  28. .resource(resource)
  29. .fetchSize(fetchSize)
  30. .timeout(timeout)
  31. .statementType(statementType)
  32. .keyGenerator(keyGenerator)
  33. .keyProperty(keyProperty)
  34. .keyColumn(keyColumn)
  35. .databaseId(databaseId)
  36. .lang(lang)
  37. .resultOrdered(resultOrdered)
  38. .resultSets(resultSets)
  39. .resultMaps(getStatementResultMaps(resultMap, resultType, id))
  40. .resultSetType(resultSetType)
  41. .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
  42. .useCache(valueOrDefault(useCache, isSelect))
  43. .cache(currentCache);
  44. ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  45. if (statementParameterMap != null) {
  46. statementBuilder.parameterMap(statementParameterMap);
  47. }
  48. MappedStatement statement = statementBuilder.build();
  49. configuration.addMappedStatement(statement);
  50. return statement;
  51. }
  1. public void addMappedStatement(MappedStatement ms) {
  2. mappedStatements.put(ms.getId(), ms);
  3. }

终于跟预料之中,把解析好的对象封装到了configuration的mappedStatements中

2、啥时候连接数据库?

既然配置都已经解析了,那什么时候链接数据库呢,我们看如下代码

  1. //3、使用工厂生产SqlSession对象
  2. SqlSession sqlSession = factory.openSession();
  3. //4、使用SqlSession创建Dao接口的代理对象
  4. UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  5. //5、使用代理对象执行方法
  6. List<User> user = userMapper.findAllUser();

首先是获取SqlSession对象,我们是通过SqlSession对象来获取UserMapper的,我们跟进去这里有没有连数据库?

  1. @Override
  2. public SqlSession openSession() {
  3. return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  4. }

这里选的是DefaultSqlSessionFactory.java的方法,为什么呢?因为在上一步生成SqlSessionFactory对象的时候

  1. public SqlSessionFactory build(Configuration config) {
  2. return new DefaultSqlSessionFactory(config);
  3. }

new的就是这个对象。

继续跟进去

  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  2. Transaction tx = null;
  3. try {
  4. final Environment environment = configuration.getEnvironment();
  5. final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  6. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  7. final Executor executor = configuration.newExecutor(tx, execType);
  8. return new DefaultSqlSession(configuration, executor, autoCommit);
  9. } catch (Exception e) {
  10. closeTransaction(tx); // may have fetched a connection so lets call close()
  11. throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
  12. } finally {
  13. ErrorContext.instance().reset();
  14. }
  15. }

上面就创建了Transaction和Executor,然后创建DefaultSqlSession对象返回,并没有进行数据库的链接,不过这个是当然的,数据库链接肯定要在只sql的时候才会创建啊,所以我们直接看数据库操作的代码

  1. //4、使用SqlSession创建Dao接口的代理对象
  2. UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

看到这里,立马就想到了肯定用了jdk的代理,生成的UserMapper肯定是代理的对象,我们跟踪进去看看

  1. @Override
  2. public <T> T getMapper(Class<T> type) {
  3. return configuration.<T>getMapper(type, this);
  4. }

这里是DefaultSqlSession.java里面的方法,因为上面创建的就是DefaultSqlSession对象,继续

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  3. if (mapperProxyFactory == null) {
  4. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  5. }
  6. try {
  7. return mapperProxyFactory.newInstance(sqlSession);
  8. } catch (Exception e) {
  9. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  10. }
  11. }

是用MapperProxyFactory来创建代理对象的,所以创建的对象肯定是MapperProxy对象,方法的执行肯定也是调用MapperProxy的invoke方法,我们跟踪进去看看

  1. public T newInstance(SqlSession sqlSession) {
  2. final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  3. return newInstance(mapperProxy);
  4. }
  1. protected T newInstance(MapperProxy<T> mapperProxy) {
  2. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  3. }

是了嘛,到这里就没必要继续下去了,动态代理。


然后我们直接看

  1. //5、使用代理对象执行方法
  2. List<User> user = userMapper.findAllUser();

这行代码,这里肯定是调用MapperProxy的invok方法

  1. @Override
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. try {
  4. if (Object.class.equals(method.getDeclaringClass())) {
  5. return method.invoke(this, args);
  6. } else if (isDefaultMethod(method)) {
  7. return invokeDefaultMethod(proxy, method, args);
  8. }
  9. } catch (Throwable t) {
  10. throw ExceptionUtil.unwrapThrowable(t);
  11. }
  12. final MapperMethod mapperMethod = cachedMapperMethod(method);
  13. return mapperMethod.execute(sqlSession, args);
  14. }
  1. return mapperMethod.execute(sqlSession, args);
  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()) {
  21. executeWithResultHandler(sqlSession, args);
  22. result = null;
  23. } else if (method.returnsMany()) {
  24. result = executeForMany(sqlSession, args);
  25. } else if (method.returnsMap()) {
  26. result = executeForMap(sqlSession, args);
  27. } else if (method.returnsCursor()) {
  28. result = executeForCursor(sqlSession, args);
  29. } else {
  30. Object param = method.convertArgsToSqlCommandParam(args);
  31. result = sqlSession.selectOne(command.getName(), param);
  32. }
  33. break;
  34. case FLUSH:
  35. result = sqlSession.flushStatements();
  36. break;
  37. default:
  38. throw new BindingException("Unknown execution method for: " + command.getName());
  39. }
  40. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  41. throw new BindingException("Mapper method '" + command.getName()
  42. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  43. }
  44. return result;
  45. }

我们这里是查询多条语句,所以走

  1. result = executeForMany(sqlSession, args);
  1. private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  2. List<E> result;
  3. Object param = method.convertArgsToSqlCommandParam(args);
  4. if (method.hasRowBounds()) {
  5. RowBounds rowBounds = method.extractRowBounds(args);
  6. result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
  7. } else {
  8. result = sqlSession.<E>selectList(command.getName(), param);
  9. }
  10. // issue #510 Collections & arrays support
  11. if (!method.getReturnType().isAssignableFrom(result.getClass())) {
  12. if (method.getReturnType().isArray()) {
  13. return convertToArray(result);
  14. } else {
  15. return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
  16. }
  17. }
  18. return result;
  19. }

继续走

  1. result = sqlSession.<E>selectList(command.getName(), param);
  1. @Override
  2. public <E> List<E> selectList(String statement, Object parameter) {
  3. return this.selectList(statement, parameter, RowBounds.DEFAULT);
  4. }

这里也是DefaultSqlSession.java里面的方法,继续

  1. @Override
  2. public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  3. try {
  4. MappedStatement ms = configuration.getMappedStatement(statement);
  5. return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  6. } catch (Exception e) {
  7. throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  8. } finally {
  9. ErrorContext.instance().reset();
  10. }
  11. }

看,这里的MappedStatement,根据statement去configuration中的mappedStatements获取的,这个是之前解析mappers的时候放进去的,获取后继续。

  1. @Override
  2. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  3. BoundSql boundSql = ms.getBoundSql(parameter);
  4. CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  5. return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  6. }

再继续

  1. @Override
  2. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  3. ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  4. if (closed) {
  5. throw new ExecutorException("Executor was closed.");
  6. }
  7. if (queryStack == 0 && ms.isFlushCacheRequired()) {
  8. clearLocalCache();
  9. }
  10. List<E> list;
  11. try {
  12. queryStack++;
  13. list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  14. if (list != null) {
  15. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  16. } else {
  17. list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  18. }
  19. } finally {
  20. queryStack--;
  21. }
  22. if (queryStack == 0) {
  23. for (DeferredLoad deferredLoad : deferredLoads) {
  24. deferredLoad.load();
  25. }
  26. // issue #601
  27. deferredLoads.clear();
  28. if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
  29. // issue #482
  30. clearLocalCache();
  31. }
  32. }
  33. return list;
  34. }

我们很容易一眼就看到查询数据库的地方

  1. list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

继续

  1. private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  2. List<E> list;
  3. localCache.putObject(key, EXECUTION_PLACEHOLDER);
  4. try {
  5. list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  6. } finally {
  7. localCache.removeObject(key);
  8. }
  9. localCache.putObject(key, list);
  10. if (ms.getStatementType() == StatementType.CALLABLE) {
  11. localOutputParameterCache.putObject(key, parameter);
  12. }
  13. return list;
  14. }
  1. @Override
  2. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  3. Statement stmt = null;
  4. try {
  5. Configuration configuration = ms.getConfiguration();
  6. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  7. stmt = prepareStatement(handler, ms.getStatementLog());
  8. return handler.<E>query(stmt, resultHandler);
  9. } finally {
  10. closeStatement(stmt);
  11. }
  12. }

这里SimpleExecutor.java,之前返回DefaultSqlSession的时候指定的。终于,历经坎坷,看到了熟悉的Statement。

  1. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  2. Statement stmt;
  3. Connection connection = getConnection(statementLog);
  4. stmt = handler.prepare(connection, transaction.getTimeout());
  5. handler.parameterize(stmt);
  6. return stmt;
  7. }

终于看到了数据库连接的地方。获取Statement后应该就可以去查数据了把,查看

  1. return handler.<E>query(stmt, resultHandler);
  1. @Override
  2. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  3. String sql = boundSql.getSql();
  4. statement.execute(sql);
  5. return resultSetHandler.<E>handleResultSets(statement);
  6. }

好了,主脉络清晰了,其它的边边角角传入参数,传出参数什么的各种配置属性,以后再慢慢看了!

 157

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2