At present, Mybatis can configure SQL in the form of annotations in addition to configuring SQL through XML. This article mainly introduces how Mybatis handles annotation SQL mapping, and analyzes the processing process through source code.
XML placement
<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>
parsing process
private void mapperElement(XNode parent) throws Exception { //If the mapper node is configured in the configuration if (parent != null) { for (XNode child : parent.getChildren()) { //If the configuration pair is a package path if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //Get the resource attribute in the mapper element String resource = child.getStringAttribute("resource"); //Get the url attribute in the mapper element String url = child.getStringAttribute("url"); //Get the class attribute in the mapper element, if the mapper configuration based on annotations is configured with class String mapperClass = child.getStringAttribute("class"); //For resource, url, mapperClass, use resource first, followed by url and finally class if (resource != null && url == null && mapperClass == null) { //create exception context ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //Create XMLMapperBuilder XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // Parse 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) { //Get the interface of mapper Class<?> mapperInterface = Resources.classForName(mapperClass); //Register the interface to a known 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); }
//Register Mapper public <T> void addMapper(Class<T> type) { //if type is an interface if (type.isInterface()) { / / Determine whether the interface has already registered the corresponding Mapper, if so, throw an exception, because the key of knownMappers is type if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //Create a MapperProxyFactory for this interface and keep it registered to knownMappers knownMappers.put(type, new MapperProxyFactory<T>(type)); //Create MapperAnnotationBuilder MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); //Parse Annotation parser.parse(); //Successful parsing means the loading is complete loadCompleted = true; } finally { // if the load is not complete remove it from 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();
//Determine whether the resource has been registered
if (!configuration.isResourceLoaded(resource)) {
//If it has not been registered, you need to load the XML resource
loadXmlResource();
/ /Add the resource name to the registered collection
configuration.addLoadedResource(resource);
//Set nameSpace
assistant.setCurrentNamespace(type.getName());
//parse cache
parseCache();
//parse cacheRef
parseCacheRef();
/ /Get
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
//If it is not a bridge method, parse the statement
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
//解析待定方法
parsePendingMethods();
}
private void loadXmlResource() { // Determine if the resource has been loaded if (!configuration.isResourceLoaded("namespace:" + type.getName())) { //Get the path of the resource 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 the resource exists if (inputStream != null) { //Build XMLMapperBuilder XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); // Parse XML xmlParser.parse (); } } }
private void parseCache() { //Get the @CacheNamespace annotation on the interface CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class); //if the annotation exists if (cacheDomain != null) { //Get the cache size of the annotation configuration Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size(); //Get the cache refresh frequency Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval(); //Parse the properties of the CacheNamespace configuration Properties props = convertToProperties(cacheDomain.properties()); //Create a cache with the data configured by the annotation assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props); } }
private void parseCacheRef() { //Get the @CacheNamespaceRef annotation on the interface CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class); //If a cache reference is configured if (cacheDomainRef != null) { //get the reference type Class<?> refType = cacheDomainRef.value(); String refName = cacheDomainRef.name(); // Throws an exception if the reference type and reference name are both null if (refType == void.class && refName.isEmpty()) { throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef"); } //If the reference type and reference name are configured with valid data at the same time, an exception will be thrown, these two are mutually exclusive data if (refType != void.class && !refName.isEmpty()) { throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef"); } //get namespace String namespace = (refType != void.class) ? refType.getName() : refName; // use cache assistant.useCacheRef(namespace); } }
void parseStatement(Method method) { //get parameter type Class<?> parameterTypeClass = getParameterType(method); //Get LanguageDriver LanguageDriver languageDriver = getLanguageDriver(method); //Get Sqlsource from annotation SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); //if sqlSource is not null if (sqlSource != null) { //Get the @Options annotation Options options = method.getAnnotation(Options.class); //create statementId final String mappedStatementId = type.getName() + "." + method.getName(); Integer fetchSize = null; Integer timeout = null; StatementType statementType = StatementType.PREPARED; ResultSetType resultSetType = ResultSetType.FORWARD_ONLY; //get sql type SqlCommandType sqlCommandType = getSqlCommandType(method); //Is it a query boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //Do you need to refresh the cache, if it is not the query default value is true, the query default value is false boolean flushCache = !isSelect; //Whether to use the cache, the query defaults to true, not the query defaults to false boolean useCache = isSelect; KeyGenerator keyGenerator; String keyProperty = "id"; String keyColumn = null; //if insert or update if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { //Get the SelectKey annotation 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 {//If it is not insert or update, that is, query and delete, then keyGenerator is NoKeyGenerator instance keyGenerator = NoKeyGenerator.INSTANCE; } //if the @Options annotation is not null if (options != null) { //Set whether to refresh the cache according to the configured value if (FlushCachePolicy.TRUE.equals(options.flushCache())) { flushCache = true; } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) { flushCache = false; } //whether to use cache 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; //Get ResultMap annotation 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 { //Get annotations of @Select, @Insert, @Update, @Delete types Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method); //Get @SelectProvider, @InsertProvider, @UpdateProvider @DeleteProvider annotations Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method); //if the SQL annotation is not null if (sqlAnnotationType != null) { //At the same time, if the sqlProvider annotation is not empty, an exception will be thrown, and the two are mutually exclusive if (sqlProviderAnnotationType != null) { throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName()); } //get annotation Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType); //Get the value of the annotation configuration final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation); //Create SqlSource based on the configured data return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) {//If the SqlProvider annotation is not empty Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); //Create a ProviderSqlSource return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method); } //If neither Sql annotation nor SqlProvider annotation is configured, return null return null; } catch (Exception e) { throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e); } }