Mybatis 基于注解Mapper源码分析
Posted wei_zw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis 基于注解Mapper源码分析相关的知识,希望对你有一定的参考价值。
目前Mybatis除了可以通过XML配置SQL外还可以通过注解的形式配置SQL,本文中主要介绍了Mybatis是如何处理注解SQL映射的,通过源码分析处理过程
XML配置
<configuration> <settings> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="useGeneratedKeys" value="true"/> </settings> <typeAliases> <typeAlias type="org.apache.ibatis.submitted.blocking_cache.Person" alias="Person" /> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="" value="" /> </transactionManager> <dataSource type="UNPOOLED"> <property name="driver" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:mem:cache" /> <property name="username" value="sa" /> </dataSource> </environment> </environments> <mappers> <mapper class="org.apache.ibatis.submitted.blocking_cache.PersonMapper"/> </mappers> </configuration>
@Insert("insert into table2 (name) values(#{name})") @SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class) int insertTable2(Name name); @Insert("insert into table2 (name) values(#{name})") @Options(useGeneratedKeys=true, keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED") int insertTable2WithGeneratedKey(Name name);
解析过程
private void mapperElement(XNode parent) throws Exception { //如果configuration中配置了mapper节点 if (parent != null) { for (XNode child : parent.getChildren()) { //如果配置对是包路径 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //获取mapper元素中的resource属性 String resource = child.getStringAttribute("resource"); //获取mapper元素中的url属性 String url = child.getStringAttribute("url"); //获取mapper元素中的class属性,如果基于注解的配置的mapper 配置的就是class String mapperClass = child.getStringAttribute("class"); //对于resource,url,mapperClass 优先使用resource,其次是url最后是class if (resource != null && url == null && mapperClass == null) { //创建异常上下文 ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //创建XMLMapperBuilder XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //解析XML mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { //获取mapper的接口 Class<?> mapperInterface = Resources.classForName(mapperClass); //将该接口注册到已知mapper configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); }
//注册Mapper public <T> void addMapper(Class<T> type) { //如果type是接口 if (type.isInterface()) { //判断该接口是否已经注册过相应的Mapper了,如果是则抛出异常,因为knownMappers的key为type if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //对该接口创建MapperProxyFactory,并保注册到knownMappers knownMappers.put(type, new MapperProxyFactory<T>(type)); //创建MapperAnnotationBuilder MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); //解析Annotation parser.parse(); //解析成功则表示加载完成 loadCompleted = true; } finally { //如果加载没有完成则将其从knownMappers中删除 if (!loadCompleted) { knownMappers.remove(type); } } } }
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) { String resource = type.getName().replace(\'.\', \'/\') + ".java (best guess)"; this.assistant = new MapperBuilderAssistant(configuration, resource); this.configuration = configuration; this.type = type; //初始化注解类型,分为两组 sqlAnnotationTypes.add(Select.class); sqlAnnotationTypes.add(Insert.class); sqlAnnotationTypes.add(Update.class); sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class); sqlProviderAnnotationTypes.add(InsertProvider.class); sqlProviderAnnotationTypes.add(UpdateProvider.class); sqlProviderAnnotationTypes.add(DeleteProvider.class); } public void parse() { String resource = type.toString(); //判断该资源是否已经注册 if (!configuration.isResourceLoaded(resource)) { //没有注册过则需要加载XML资源 loadXmlResource(); //将该资源名称添加到已经注册集合中 configuration.addLoadedResource(resource); //设置nameSpace assistant.setCurrentNamespace(type.getName()); //解析cache parseCache(); //解析cacheRef parseCacheRef(); //获取 Method[] methods = type.getMethods(); for (Method method : methods) { try { //如果不是bridge方法则解析statement if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } //解析待定方法 parsePendingMethods(); }
如果存在XML配置也是会加载的
private void loadXmlResource() { //判断资源是否有加载过 if (!configuration.isResourceLoaded("namespace:" + type.getName())) { //获取资源的path String xmlResource = type.getName().replace(\'.\', \'/\') + ".xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e) { // ignore, resource is not required } //如果资源存在 if (inputStream != null) { //构建XMLMapperBuilder XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); //解析XML xmlParser.parse(); } } }
下图是同时存在XML和annotation的情况
private void parseCache() { //获取接口上 @CacheNamespace注解 CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class); //如果注解存在 if (cacheDomain != null) { //获取注解配置的缓存大小 Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size(); //获取缓存刷新频率 Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval(); //解析CacheNamespace配置的属性 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) { //获取引用的类型 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"); } //获取namespace String namespace = (refType != void.class) ? refType.getName() : refName; //使用缓存 assistant.useCacheRef(namespace); } }
void parseStatement(Method method) { //获取参数类型 Class<?> parameterTypeClass = getParameterType(method); //获取LanguageDriver LanguageDriver languageDriver = getLanguageDriver(method); //从注解中获取Sqlsource SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); //如果sqlSource不为null if (sqlSource != null) { //获取 @Options注解 Options options = method.getAnnotation(Options.class); //创建statementId final String mappedStatementId = type.getName() + "." + method.getName(); Integer fetchSize = null; Integer timeout = null; StatementType statementType = StatementType.PREPARED; ResultSetType resultSetType = ResultSetType.FORWARD_ONLY; //获取sql类型 SqlCommandType sqlCommandType = getSqlCommandType(method); //是否是查询 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //是否需要刷新缓存,如果不是查询默认值为true,查询默认值为false boolean flushCache = !isSelect; //是否使用缓存,查询默认为true,不是查询默认为false boolean useCache = isSelect; KeyGenerator keyGenerator; String keyProperty = "id"; String keyColumn = null; //如果是插入或更新 if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { //获取SelectKey注解 SelectKey selectKey = method.getAnnotation(SelectKey.class); if (selectKey != null) { 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 {//不是插入或更新 即查询和删除则keyGenerator为NoKeyGenerator实例 keyGenerator = NoKeyGenerator.INSTANCE; } //如果@Options注解不为null if (options != null) { //根据配置的值设置是否刷新缓存 if (FlushCachePolicy.TRUE.equals(options.flushCache())) { flushCache = true; } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) { flushCache = false; } //是否使用缓存 useCache = options.useCache(); //fetchSize fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348 timeout = options.timeout() > -1 ? options.timeout() : null; statementType = options.statementType(); resultSetType = options.resultSetType(); } String resultMapId = null; //获取ResultMap注解 ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class); if (resultMapAnnotation != null) { String[] resultMaps = resultMapAnnotation.value(); StringBuilder sb = new StringBuilder(); for (String resultMap : resultMaps) { if (sb.length() > 0) { sb.append(","); } sb.append(resultMap); } resultMapId = sb.toString(); } else if (isSelect) { resultMapId = parseResultMap(method); } 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); } }
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) { try { //获取@Select, @Insert, @Update, @Delete类型的注解 Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method); //获取 @SelectProvider, @InsertProvider, @UpdateProvider @DeleteProvider注解 Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method); //如果SQL注解不为null if (sqlAnnotationType != null) { //同时sqlProvider注解也不为空则抛出异常,两者互斥 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); //获取注解配置的值 final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation); //根据配置的数据创建SqlSource return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) {//如果SqlProvider注解不为空 Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); //创建一个ProviderSqlSource return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method); } //如果没有配置Sql注解也没有配置SqlProvider注解则返回null return null; } catch (Exception e) { throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e); } }
以上是关于Mybatis 基于注解Mapper源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Mybatis Mapper接口是如何找到实现类的-源码分析
MyBatis中的@Mapper注解及配套注解使用详解(上)