(五) mybatis 源码之 注解 SQL

注解型 SQL

众所周知, mybatis 支持两种编写 SQL 的方式 : xml 配置文件和注解, 就像这样

@Results(id = "stu", value = {
    @Result(id = true, column = "id", property = "id"),
    @Result(column = "name", property = "name"),
    @Result(column = "tel", property = "tel"),
    @Result(column = "address", property = "address")
})
@Select("select * from student where id = #{id}")
Student getStudentWithAnnotation(Integer id);

如果使用这种方式, 耦合性太强了, 且不能在全局配置文件中引入 mapper.xml , 使用 package, 或者 class

<mappers>
    <mapper class="com.gp.ibatis.dao.StudentDao"/>
    <package name="com.gp.ibatis.dao"/>-->
</mappers>

前面我们知道, 再解析 <mappers> 标签的时候, 为 <package> 单独处理, 回过头去看

// 直接 <package> 的指定路径拿到, 调用了 MapperRegistry 的 addMappers(String, Class) 方法
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 使用 VFS 这个 IO 工具类, 得到想要的 Class 对象, 放在 ResolverUtil 对象中
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 使用 Set 去重
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}
------------------------------------------
// 接着看他的 find 方法
public ResolverUtil<T> find(Test test, String packageName) {
    // 绝对路径
    String path = getPackagePath(packageName);

    try {
        // 拿到此路径下的所有文件, VFS 用于处理 IO
        List<String> children = VFS.getInstance().list(path);
        for (String child : children) {
            // 文件匹配, 只要后缀名为 .class 的文件
            if (child.endsWith(".class")) {
                addIfMatching(test, child);
            }
        }
    }
    catch (IOException ioe) {
        log.error("Could not read package: " + packageName, ioe);
    }

    return this;
}
-----------------------------
// 接着看 addIfMatching 方法
protected void addIfMatching(Test test, String fqn) {
    try {
        // 将路径 / 替换成 . (变成全类名的方式)
        String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
        ClassLoader loader = getClassLoader();
        if (log.isDebugEnabled()) {
            log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
        }
        // 使用类加载器将这个全类名加载到 JVM 中 (并未初始化)
        Class<?> type = loader.loadClass(externalName);
        if (test.matches(type)) {
            // 放到此集合中
            matches.add((Class<T>) type);
        }
    }
    catch (Throwable t) {
        log.warn("Could not examine class '" + fqn + "'" + " due to a "
                 + t.getClass().getName() + " with message: " + t.getMessage());
    }
}

接着看 MapperRegistry 的 addMapper(Class) 方法

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));
        // 必须注意的是 : 此类型必须在解析器执行前添加到 Configuration 对象中
        // 如果此类型已知, 则不会处理
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 解析注解方法
        parser.parse();
        loadCompleted = true;
    }
    finally {
        if (!loadCompleted) {
            knownMappers.remove(type);
        }
    }
}
// 继续查看 MapperAnnotationBuilder 的 parse 方法
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 生成一个 xml 配置文件名 (并没有存放在磁盘)
        // 然后转换成字节流 (类似于解析全局配置文件)
        loadXmlResource();
        // 下面类似于解析 mapper.xml 配置文件
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        for (Method method : type.getMethods()) {
            if (!canHaveStatement(method)) {
                continue;
            }
            // 如果接口的方法存在 mybatis 的注解, 解析此方法
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                && method.getAnnotation(ResultMap.class) == null) {
                parseResultMap(method);
            }
            try {
                // 封装成 MappedStatement 对象放入 Configuration 对象中
                parseStatement(method);
            }
            catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}

自此, Mapper 的方法被封装成一个个 MappedStatement 对象, 存放在 Configuration 对象中, 只要拿到这个 MappedStatement 对象, mybatis 就能拿到各种信息, 比如 SQL 语句, StatementType , 参数 …

猜你喜欢

转载自blog.csdn.net/Gp_2512212842/article/details/107662423