Mybatis实现动态xml编译

解决问题

实际开发过程中使用mybatis自定义sql, 在项目运行过程中发现sql存在问题或者需要对已有sql进行优化, 这时候改完sql都需要进行项目的重启才能把最新的sql应用到项目中, 这样会因为修改一个sql而进行项目重启非常的不方便, 那么mybatis中的xml可以在不重启项目的情况下进行重新加载吗?

mybatis是如何进行xml加载的

  1. mybatis首先会读取mybatis-config.xml,并进行解析mapper节点得到需要加载 mapper.xml
    XmlConfigBuilder.java
private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      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"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
  1. mapperElement 对mappers节点进行解析将 xml配置加载到mybatis核心configuration中
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              /*byte[] bytes = new byte[1024];
              while ((inputStream.read(bytes))>0){
                System.out.println(new String(bytes));
              }*/
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            //获取配置的mybatis-config.xml文件输入流(无缓存)
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

这里可以看出mybatis-config.xml中的mapper.xml配置是支持三种方式:resource,url,mapperClass的,这里以url为例

  public void parse() {
    //查询是否已经存在已有的文档加载对象,如果没有则进行MapperRegistry初始化
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

这里是对xml进行解析和加载

3.当mybatis加载完配置以后会生成SqlSessionFactory然后通过SqlSessionFactory去获取数据库连接会话了,之后就是进行数据库操作就与本文无关了, 既然我们知道了mapper.xml的加载过程, 那么我们就可以在程序运行过程中对mapper.xml进行二次加载, 我们需要对mybatis的核心配置类Configuration.java中的一些存储xml配置的缓存进行清除并重新加载最新的xml到容器中(mapperRegistry,mappedStatements)

  protected String databaseId;
  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
   */
  protected Class<?> configurationFactory;

  //mapperRegistry mapper注册表
  protected MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  
  //mapper 描述信息
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

4.源码中这个两个对象并没有提供清除和修改值的方法所以需要手动修改源码添加(或者利用反射进行设置值)

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //从mapperRegistry中获取对应mapper的接口代理对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //动态代理创建mapper接口实现类实例,并重写对应得xml接口方法与查询实现
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  //自定义增强方法
  public void clearCache(){
    knownMappers.clear();
  }
public class Configuration {
  //自定义增强方法
  public void clearMappedStatement() {
    mappedStatements.clear();
  }
  
  //自定义增强方法
  public void setMapperRegistry(MapperRegistry mapperRegistry) {
    this.mapperRegistry = mapperRegistry;
  }
  
  //自定义增强方法
  public void addMappedStatements(Collection<String> keys,Configuration configuration) {
    for (String key : keys){
      mappedStatements.put(key, configuration.getMappedStatement(key));
    }
  }

  //自定义增强方法
 public void addLoadedResources(Set<String> resources) {
    for (String resource : resources){
      addLoadedResource(resource);
    }
  }

5.到此源码修改结束进行测试

public class App {
    public static void main( String[] args ) throws IOException {
        Scanner scanner = new Scanner(System.in);
        //1\读取配置文件
        String resource = "mybatis-config.xml";
        String resourceMapper = "mybatis-config-mapper.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //2、初始化mybatis,创建SqlSessionFactory类实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        Configuration configuration = sqlSessionFactory.getConfiguration();
        configuration.setMapUnderscoreToCamelCase(true);
        //3、创建Session实例
        SqlSession session = sqlSessionFactory.openSession();
        AMapper aMapper = session.getMapper(AMapper.class);
        System.out.println(aMapper.getName());

        while ((scanner.nextInt()==1)){
            //清除xml缓存
            sqlSessionFactory.getConfiguration().getMapperRegistry().clearCache();
            sqlSessionFactory.getConfiguration().clearMappedStatement();
            //重新加载xml
            InputStream inputStream2 = Resources.getResourceAsStream(resource);
            XMLConfigBuilder mapperBuilder = new XMLConfigBuilder(inputStream2, null, null);
            configuration = mapperBuilder.parseMapper();
            sqlSessionFactory.getConfiguration().setMapperRegistry(configuration.getMapperRegistry());
            sqlSessionFactory.getConfiguration().addMappedStatements(configuration.getMappedStatementNames(),configuration);
            sqlSessionFactory.getConfiguration().addLoadedResources(configuration.getLoadedResources());
            //3、创建Session实例
            session = sqlSessionFactory.openSession();
            AMapper aMapper1 = session.getMapper(AMapper.class);
            System.out.println(aMapper1.getName());
        }


    }
}

如下是没有重启应用的结果

Opening JDBC Connection
Created connection 1763344271.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@691a7f8f]
==>  Preparing: select name from a where id = 2
==> Parameters: 
<==    Columns: name
<==        Row: tom
<==      Total: 1
tom
1
Opening JDBC Connection
Created connection 1405747618.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@53ca01a2]
==>  Preparing: select name from a where id = 1
==> Parameters: 
<==    Columns: name
<==        Row: timi
<==      Total: 1
timi

执行sql已经发生变化

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容