zoukankan      html  css  js  c++  java
  • Mybatis 可以同时使用 XML 和注解吗?

    结论

    MyBatis 可以同时使用 XML 和注解的方式配置

    假设项目中有一个Mapper:com.inaction.webmybatisinaction.UserMapper 和他的XML配置文件放置在resource目录下:UserMapper.xml

    方式一

    只写明 XM L的 resource 路径(或者URL路径)

    <mappers>
        <mapper resource="UserMapper.xml"/>
     </mappers>
    

    方式二:

    只写明注解 Mapper 的类全路径名(这种方式只适合于只包含注解的配置)

    <mappers>
        <mapper class="com.inaction.webmybatisinaction.UserMapper"/>
    </mappers>
    

    方式三:

    同时都注明,但是类全路径名必须写在 xml 的前面

    <mappers>
        <mapper class="com.inaction.webmybatisinaction.UserMapper"/>
        <mapper resource="UserMapper.xml"/>
    </mappers>
    

    注意:

    虽然可以同时采用XML和注解两种方式配置,但是不能同时对同一个方法既注解又XML配置,不然会报错。

    原因分析

    方式一成立的原因

    在SqlSessionFactory创建的过程中,会先创建Configuration对象,会先解析SqlMapConfig.xml中的节点,最后解析的就是节点,其中会调用XMLMapperBuilder的parse()方法解析,当解析了XML的方式节点时,会在解析XML文件配置到Configuration中之后进行一个命名空间绑定的操作: bindMapperForNamespace();

    //java的XMLMapperBuilder类
    public void parse() {
      if (!configuration.isResourceLoaded(resource)) {
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
      }
    
      parsePendingResultMaps();
      parsePendingCacheRefs();
      parsePendingStatements();
    }
    private void bindMapperForNamespace() {
      String namespace = builderAssistant.getCurrentNamespace();
      if (namespace != null) {
        Class<?> boundType = null;
        try {
          boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
          //ignore, bound type is not required
        }
        if (boundType != null) {
          if (!configuration.hasMapper(boundType)) {
          //这里如果检测到之前注册过Mapper之后就不会重复注册了也不会报错
            // Spring may not know the real resource name so we set a flag
            // to prevent loading again this resource from the mapper interface
            // look at MapperAnnotationBuilder#loadXmlResource
            configuration.addLoadedResource("namespace:" + namespace);
            configuration.addMapper(boundType);
          }
        }
      }
    }
    
    

    该操作首先会判断Configuration有没有事先解析过Mapper对象,如果事先解析过则不做处理直接退出,如果没有解析过他则会通过XML文件中配置的命名空间反射到对应的Mapper类,然后通过一系列的反射操作解析注解。所以,只写明XML文件路径依然是可以解析到Mapper注解。

    方式二的局限

    这种方式会直接向Configuration的MapperRegistry注册Mapper,但是由于Mapper对象不知道XML的位置所欲不会解析XML中的配置。故这种方式是不安全的。

    //这个方法位于MapperRegistry类之中,只会解析Mapper注解不会解析XML
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
          if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    
    

    方式三成立的原因

    方式三一定要把类的配置写在xml的配置之前,说先解析完mapper之后,可以继续解析xml,解析xml时如果判断mapper解析过之后则不会重复解析也不会抛错,但是如果先解析xml,会向Configuration中注册Mapper,当之后解析Mapper时如果检测到有加载过则会抛出异常并终止程序创建SqlSessionFactory。

    //这段代码参考第二点分析,这里会调用hasMapper(type)检测是否已经注册了Mapper,如果解析了就会抛错。
     public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
          if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
        }
     }
    

    注意
    虽然可以同时采用XML和注解两种方式配置,但是不能同时对同一个方法既注解又XML配置,不然会报错。因为在解析每个sqlmap的时候会给其生成唯一的ID,并存入MapperRegistry中,这个注册中心本质上是一个HashMap,且不允许插入已经存在的key值,做插入操作时如果检测到已存在同名ID就会报错终止解析。所以不允许对一个方法既注解又XML配置。

    //这个是Configuration类中的方法,用于讲语句注册进来
    public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
    }
    
    //这个是mappedStatements的内部实现类StictMap的实现方法,其中第一步就是检验重复并报错
    	@Override
        @SuppressWarnings("unchecked")
        public V put(String key, V value) {
          if (containsKey(key)) {
            throw new IllegalArgumentException(name + " already contains value for " + key
                + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
          }
          if (key.contains(".")) {
            final String shortKey = getShortName(key);
            if (super.get(shortKey) == null) {
              super.put(shortKey, value);
            } else {
              super.put(shortKey, (V) new Ambiguity(shortKey));
            }
          }
          return super.put(key, value);
        }
    
    作者:Binge
    本文版权归作者和博客园共有,转载必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
  • 相关阅读:
    计蒜客 聪明的班主任(思维)
    codeforces 456 E. Civilization(并查集+数的直径)
    codeforces 456 D. A Lot of Games(字典数+博弈+思维+树形dp)
    codeforces 233 D. Table(思维+dp )
    codeforces 233 C. Cycles(贪心+思维)
    codeforces 814 D. An overnight dance in discotheque (贪心+bfs)
    codeforces 814 C. An impassioned circulation of affection(二分+思维)
    codeforces 813 D. Two Melodies(dp)
    Atcoder F
    Java正则表达式
  • 原文地址:https://www.cnblogs.com/binbingg/p/15343588.html
Copyright © 2011-2022 走看看