第八章-Mybatis源码解析-以注解方式走流程

先看一段示例代码

static SqlSession javaStart(){
    // dataSource 自己创建一个,用什么都行,比如Druid
    DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
    // 事务功能也可以自己定义的,这里用 JdbcTransactionFactory ,因为Mybatis默认就是这个
    TransactionFactory transactionFactory = new JdbcTransactionFactory();
    // 先创建环境
    Environment environment = new Environment("development", transactionFactory, dataSource);
    // 创建 configuration
    Configuration configuration = new Configuration(environment);
    // ======= 以上内容,在`第二章`,也有对应的xml方式的实现,基本都一样,下面开始才是xml和注解方式的差异=========
    // 将mapper添加到configuration的容器中,这个方法的描述在`5.2.2`中有涉及,最终会走到 MapperAnnotationBuilder.parse(),解析注解的Mapper,细节请看章节`8.1`
    configuration.addMapper(BlogMapper.class);
    configuration.addMapper(UserMapper.class);
    // 构建SqlSessionFactory,章节`2.1`中有描述
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
    // 创建一个 SqlSession
    return sqlSessionFactory.openSession();
}

8.1 mapper解析

public void parse() {
    // Class 类名转成字符串,前面会有一个前缀:"interface "
    String resource = type.toString();
    // 判断是否重复加载
    if (!configuration.isResourceLoaded(resource)) {
        // 先加载对应类名的xml资源,这也就是为什么,我们在开发时,只写了一个接口类XXXMapper.java,在调用该接口类时,就能执行到同名XXXMapper.xml文件中同名的mapper语句
        loadXmlResource();
        // 解析完成,把资源添加到 configuration 里的 set集合,证明已加载过,判断重复时有用
        configuration.addLoadedResource(resource);
        // 设置当前类名为命名空间
        assistant.setCurrentNamespace(type.getName());
        // 解析注解的缓存
        parseCache();
        // 解析注解的缓存引用
        parseCacheRef();
        // 通过反射技术,遍历该接口类的所有方法
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            try {
                // issue #237
                /* 这里有一个brige(桥接)方法的判断,什么是桥接方法呢?jvm中有描述,就是修饰符标记为:
                #define JVM_ACC_BRIDGE        0x0040  (bridge method generated by compiler )
                意思是桥接方法由编译器生成,在JVM中什么情况下会生成桥接代码呢?其实就是通过方法签名(方法+参数)无法唯一确定一个函数时,编译器就会默认再生成一个同名的桥接方法。一般在子类继承父类方法时,改变了返回值;还有一种是参数类型是泛型,被擦除了类型后。这里以第一种情况举例,代码看`图8-1`,生成的字节码看`图8-2`
                */
                if (!method.isBridge()) {
                    // 解析出sql语句,看`8.1.1`
                    parseStatement(method);
                }
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}

private void loadXmlResource() {
    // 先判断是否重复加载
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        // 查找资源
        String xmlResource = type.getName().replace('.', '/') + ".xml";
        // #1347
        InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
        if (inputStream == null) {
            // Search XML mapper that is not in the module but in the classpath.
            try {
                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
            } catch (IOException e2) {
                // ignore, resource is not required
            }
        }
        if (inputStream != null) {
            // 创建 XMLMapperBuilder 对象,看`3.1`有介绍,并解析xml
            XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
            // xml解析,看`3.2`有介绍
            xmlParser.parse();
        }
    }
}


private void parseCache() {
    // 类型上有 CacheNamespace 的注解
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    if (cacheDomain != null) {
        // 下面这些参数,是不是很眼熟,看看前面章节`3.2.2.2`
        Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
        Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
        Properties props = convertToProperties(cacheDomain.properties());
        assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
    }
}


private void parseCacheRef() {
     // 类型上有 CacheNamespaceRef 的注解
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
    if (cacheDomainRef != null) {
        // 下面这些参数,是不是很眼熟,看看前面章节`3.2.2.1`
        Class<?> refType = cacheDomainRef.value();
        String refName = cacheDomainRef.name();
        if (refType == void.class && refName.isEmpty()) {
            throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
        }
        if (refType != void.class && !refName.isEmpty()) {
            throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
        }
        String namespace = (refType != void.class) ? refType.getName() : refName;
        try {
            assistant.useCacheRef(namespace);
        } catch (IncompleteElementException e) {
            configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
        }
    }
}

图8-1
在这里插入图片描述

图8-2
在这里插入图片描述

8.1.1 parseStatement

void parseStatement(Method method) {
    // 该方法对应的参数类型
    Class<?> parameterTypeClass = getParameterType(method);
    // 这里还是用的 XMLLanguageDriver
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 从注解中解析出Sql并封装成SqlSource,看章节`8.1.2`
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
        // Options 注解
        Options options = method.getAnnotation(Options.class);
        // 映射语句id
        final String mappedStatementId = type.getName() + "." + method.getName();
        Integer fetchSize = null;
        Integer timeout = null;
        // 设置默认类型
        StatementType statementType = StatementType.PREPARED;
        // resultSetType	FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
        ResultSetType resultSetType = configuration.getDefaultResultSetType();
        // sql类型:select|update|insert|delete
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        // 判断是否是select
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = !isSelect; // 刷新缓存标志,只有非select语句才有
        boolean useCache = isSelect; // 是否缓存标志,只有select语句才有

        KeyGenerator keyGenerator;
        String keyProperty = null;
        String keyColumn = null;
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // insert/update时,需要考虑SelectKey的情况
            // first check for SelectKey annotation - that overrides everything else
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if (selectKey != null) {
                // 参考章节`3.2.2.7`
                keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            } else if (options == null) {
                keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            } else {
                keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
                keyProperty = options.keyProperty();
                keyColumn = options.keyColumn();
            }
        } else {
            // select/delete时,不需要考虑selectkey的情况
            keyGenerator = NoKeyGenerator.INSTANCE;
        }
	    // Option注解中,设置了一些基础参数值,比如超时时间、刷新缓存标志、使用缓存标志、语句类型、读取数据库数据量大小等,下面就是对这些参数的设置,这些参数,前面讲解xml方式时,都有讲,不做赘述了
        if (options != null) {
            if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
                flushCache = true;
            } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
                flushCache = false;
            }
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            if (options.resultSetType() != ResultSetType.DEFAULT) {
                resultSetType = options.resultSetType();
            }
        }

        // 处理注解的 ResultMap
        String resultMapId = null;
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {
            resultMapId = String.join(",", resultMapAnnotation.value());
        } else if (isSelect) {
            resultMapId = parseResultMap(method);
        }
	    // 创建 MappedStatement 并添加到 configuration 中,看章节`3.2.2.6`,这里有一点要说明一下,如果xml中和注解中都有同名的sql,会抛出已包含异常,搜索关键字:“already contains value for”
        assistant.addMappedStatement(
            mappedStatementId,
            sqlSource,
            statementType,
            sqlCommandType,
            fetchSize,
            timeout,
            // ParameterMapID
            null,
            parameterTypeClass,
            resultMapId,
            getReturnType(method),
            resultSetType,
            flushCache,
            useCache,
            // TODO gcode issue #577
            false,
            keyGenerator,
            keyProperty,
            keyColumn,
            // DatabaseID
            null,
            languageDriver,
            // ResultSets
            options != null ? nullOrEmpty(options.resultSets()) : null);
    }
}

8.1.2 getSqlSourceFromAnnotations

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
        // 拿到注解类型:select|insert|update|delete
        Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
        // sql提供者类型,顾名思意,也就是把一个接口一分为二,sql有提供者提供,接口负责传参,类型主要有4种:SelectProvider|InsertProvider|UpdateProvider|DeleteProvider
        Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
        if (sqlAnnotationType != null) {
            // sqlAnnotationType 和 sqlProviderAnnotationType 两者只能出现一个
            if (sqlProviderAnnotationType != null) {
                throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
            }
            // 取出对应的注解对象
            Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
            // 这一步就是取出注解上的值,这里是指在注解中写的sql语句
            final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
            return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
        } else if (sqlProviderAnnotationType != null) {
            Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
            return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
        }
        // 没有注解,那直接返回null
        return null;
    } catch (Exception e) {
        throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
}


private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
    final StringBuilder sql = new StringBuilder();
    for (String fragment : strings) {
        sql.append(fragment);
        sql.append(" ");
    }
    // 这一步可以参考章节`3.2.2.6`
    return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
}

至此,xml和注解方式的都讲完了,后续章节再讲一个插件,Mybatis源码就算完结了。

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多栖码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值