Caratacus 8 년 전
부모
커밋
e8c4a7b7c8

+ 6 - 1
mybatis-plus/src/main/java/com/baomidou/mybatisplus/MybatisConfiguration.java

@@ -24,6 +24,8 @@ import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.session.SqlSession;
 
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
 import java.util.logging.Logger;
 
 /**
@@ -59,7 +61,10 @@ public class MybatisConfiguration extends Configuration {
 	 * Mapper 注册
 	 */
 	public final MybatisPulsMapperRegistry mybatisPulsMapperRegistry = new MybatisPulsMapperRegistry(this);
-
+	/**
+	 * 缓存注册标识
+	 */
+	public static Set<String> MAPPER_REGISTRY_CACHE = new ConcurrentSkipListSet<String>();
 	/*
 	 * 元对象字段填充控制器
 	 */

+ 547 - 558
mybatis-plus/src/main/java/com/baomidou/mybatisplus/MybatisPlusMapperBuilder.java

@@ -1,38 +1,21 @@
 /**
- *    Copyright 2009-2016 the original author or authors.
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
+ * Copyright 2009-2016 the original author or authors.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 package com.baomidou.mybatisplus;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Array;
-import java.lang.reflect.GenericArrayType;
-import java.lang.reflect.Method;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
+import com.baomidou.mybatisplus.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Arg;
 import org.apache.ibatis.annotations.CacheNamespace;
 import org.apache.ibatis.annotations.CacheNamespaceRef;
@@ -45,6 +28,7 @@ import org.apache.ibatis.annotations.InsertProvider;
 import org.apache.ibatis.annotations.Lang;
 import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Options.FlushCachePolicy;
 import org.apache.ibatis.annotations.Result;
 import org.apache.ibatis.annotations.ResultMap;
 import org.apache.ibatis.annotations.ResultType;
@@ -55,7 +39,6 @@ import org.apache.ibatis.annotations.SelectProvider;
 import org.apache.ibatis.annotations.TypeDiscriminator;
 import org.apache.ibatis.annotations.Update;
 import org.apache.ibatis.annotations.UpdateProvider;
-import org.apache.ibatis.annotations.Options.FlushCachePolicy;
 import org.apache.ibatis.binding.BindingException;
 import org.apache.ibatis.binding.MapperMethod.ParamMap;
 import org.apache.ibatis.builder.BuilderException;
@@ -64,7 +47,6 @@ import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
 import org.apache.ibatis.builder.annotation.MethodResolver;
 import org.apache.ibatis.builder.annotation.ProviderSqlSource;
-import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.cursor.Cursor;
 import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
 import org.apache.ibatis.executor.keygen.KeyGenerator;
@@ -89,538 +71,545 @@ import org.apache.ibatis.type.JdbcType;
 import org.apache.ibatis.type.TypeHandler;
 import org.apache.ibatis.type.UnknownTypeHandler;
 
-import com.baomidou.mybatisplus.mapper.BaseMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * 继承 MapperAnnotationBuilder 没有XML配置文件注入基础CRUD方法
- * 
+ *
  * @author Caratacus
  */
 public class MybatisPlusMapperBuilder extends MapperAnnotationBuilder {
 
-	private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
-	private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();
-
-	private Configuration configuration;
-	private MapperBuilderAssistant assistant;
-	private Class<?> type;
-
-	public MybatisPlusMapperBuilder(Configuration configuration, Class<?> type) {
-		super(configuration, 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)) {
-			loadXmlResource();
-			configuration.addLoadedResource(resource);
-			assistant.setCurrentNamespace(type.getName());
-			parseCache();
-			parseCacheRef();
-			Method[] methods = type.getMethods();
-			inspectInject();
-			for (Method method : methods) {
-				try {
-					// issue #237
-					if (!method.isBridge()) {
-						parseStatement(method);
-					}
-				} catch (IncompleteElementException e) {
-					configuration.addIncompleteMethod(new MethodResolver(this, method));
-				}
-			}
-		}
-		parsePendingMethods();
-	}
-
-	/*
-	 * 注入 CURD 动态 SQL
-	 */
-	private void inspectInject() {
-		if (BaseMapper.class.isAssignableFrom(type)) {
-            MybatisConfiguration.SQL_INJECTOR.inject(configuration, assistant, type);
+    private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
+    private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();
+
+    private Configuration configuration;
+    private MapperBuilderAssistant assistant;
+    private Class<?> type;
+
+    public MybatisPlusMapperBuilder(Configuration configuration, Class<?> type) {
+        super(configuration, 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)) {
+            boolean existXml = loadXmlResource();
+            configuration.addLoadedResource(resource);
+            assistant.setCurrentNamespace(type.getName());
+            parseCache();
+            parseCacheRef();
+            Method[] methods = type.getMethods();
+            for (Method method : methods) {
+                try {
+                    // issue #237
+                    if (!method.isBridge()) {
+                        parseStatement(method);
+                    }
+                } catch (IncompleteElementException e) {
+                    configuration.addIncompleteMethod(new MethodResolver(this, method));
+                }
+            }
+           inspectInject(existXml);
+        }
+        parsePendingMethods();
+    }
+
+    /*
+     * 注入 CURD 动态 SQL(XML不存在时注入)
+     */
+    private void inspectInject(boolean flag) {
+        if (!flag && BaseMapper.class.isAssignableFrom(type)) {
+            MybatisConfiguration.SQL_INJECTOR.inspectInject(configuration, assistant, type);
+        }
+    }
+
+    private void parsePendingMethods() {
+        Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
+        synchronized (incompleteMethods) {
+            Iterator<MethodResolver> iter = incompleteMethods.iterator();
+            while (iter.hasNext()) {
+                try {
+                    iter.next().resolve();
+                    iter.remove();
+                } catch (IncompleteElementException e) {
+                    // This method is still missing a resource
+                }
+            }
+        }
+    }
+
+    /**
+     * 是否存在XML
+     *
+     * @return boolean ture存在,flase不存在
+     */
+    private boolean loadXmlResource() {
+        boolean flag = true;
+        // Spring may not know the real resource name so we check a flag
+        // to prevent loading again a resource twice
+        // this flag is set at MybatisXMLMapperBuilder#bindMapperForNamespace
+        if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
+            String xmlResource = type.getName().replace('.', '/') + ".xml";
+            InputStream inputStream = null;
+            try {
+                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
+            } catch (IOException e) {
+                // ignore, resource is not required
+                flag = false;
+            }
+            if (inputStream != null) {
+                MybatisXMLMapperBuilder xmlParser = new MybatisXMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource,
+                        configuration.getSqlFragments(), type.getName());
+                xmlParser.parse();
+            }
+        }
+        return flag;
+    }
+
+    private void parseCache() {
+        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();
+            assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size,
+                    cacheDomain.readWrite(), cacheDomain.blocking(), null);
+        }
+    }
+
+    private void parseCacheRef() {
+        CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
+        if (cacheDomainRef != null) {
+            assistant.useCacheRef(cacheDomainRef.value().getName());
+        }
+    }
+
+    private String parseResultMap(Method method) {
+        Class<?> returnType = getReturnType(method);
+        ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);
+        Results results = method.getAnnotation(Results.class);
+        TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
+        String resultMapId = generateResultMapName(method);
+        applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);
+        return resultMapId;
+    }
+
+    private String generateResultMapName(Method method) {
+        Results results = method.getAnnotation(Results.class);
+        if (results != null && !results.id().isEmpty()) {
+            return type.getName() + "." + results.id();
+        }
+        StringBuilder suffix = new StringBuilder();
+        for (Class<?> c : method.getParameterTypes()) {
+            suffix.append("-");
+            suffix.append(c.getSimpleName());
+        }
+        if (suffix.length() < 1) {
+            suffix.append("-void");
+        }
+        return type.getName() + "." + method.getName() + suffix;
+    }
+
+    private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results,
+                                TypeDiscriminator discriminator) {
+        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
+        applyConstructorArgs(args, returnType, resultMappings);
+        applyResults(results, returnType, resultMappings);
+        Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
+        // TODO add AutoMappingBehaviour
+        assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
+        createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
+    }
+
+    private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
+        if (discriminator != null) {
+            for (Case c : discriminator.cases()) {
+                String caseResultMapId = resultMapId + "-" + c.value();
+                List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
+                // issue #136
+                applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
+                applyResults(c.results(), resultType, resultMappings);
+                // TODO add AutoMappingBehaviour
+                assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
+            }
+        }
+    }
+
+    private Discriminator applyDiscriminator(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
+        if (discriminator != null) {
+            String column = discriminator.column();
+            Class<?> javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType();
+            JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
+            @SuppressWarnings("unchecked")
+            Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) (discriminator.typeHandler() == UnknownTypeHandler.class ? null
+                    : discriminator.typeHandler());
+            Case[] cases = discriminator.cases();
+            Map<String, String> discriminatorMap = new HashMap<String, String>();
+            for (Case c : cases) {
+                String value = c.value();
+                String caseResultMapId = resultMapId + "-" + value;
+                discriminatorMap.put(value, caseResultMapId);
+            }
+            return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
+        }
+        return null;
+    }
+
+    void parseStatement(Method method) {
+        Class<?> parameterTypeClass = getParameterType(method);
+        LanguageDriver languageDriver = getLanguageDriver(method);
+        SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
+        if (sqlSource != null) {
+            Options options = method.getAnnotation(Options.class);
+            final String mappedStatementId = type.getName() + "." + method.getName();
+            Integer fetchSize = null;
+            Integer timeout = null;
+            StatementType statementType = StatementType.PREPARED;
+            ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
+            SqlCommandType sqlCommandType = getSqlCommandType(method);
+            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
+            boolean flushCache = !isSelect;
+            boolean useCache = isSelect;
+            KeyGenerator keyGenerator;
+            String keyProperty = "id";
+            String keyColumn = null;
+            if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
+                // first check for SelectKey annotation - that overrides
+                // everything else
+                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() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
+                } else {
+                    keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
+                    keyProperty = options.keyProperty();
+                    keyColumn = options.keyColumn();
+                }
+            } else {
+                keyGenerator = new NoKeyGenerator();
+            }
+            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();
+                resultSetType = options.resultSetType();
+            }
+            String resultMapId = null;
+            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 LanguageDriver getLanguageDriver(Method method) {
+        Lang lang = method.getAnnotation(Lang.class);
+        Class<?> langClass = null;
+        if (lang != null) {
+            langClass = lang.value();
+        }
+        return assistant.getLanguageDriver(langClass);
+    }
+
+    private Class<?> getParameterType(Method method) {
+        Class<?> parameterType = null;
+        Class<?>[] parameterTypes = method.getParameterTypes();
+        for (Class<?> currentParameterType : parameterTypes) {
+            if (!RowBounds.class.isAssignableFrom(currentParameterType)
+                    && !ResultHandler.class.isAssignableFrom(currentParameterType)) {
+                if (parameterType == null) {
+                    parameterType = currentParameterType;
+                } else {
+                    // issue #135
+                    parameterType = ParamMap.class;
+                }
+            }
+        }
+        return parameterType;
+    }
+
+    private Class<?> getReturnType(Method method) {
+        Class<?> returnType = method.getReturnType();
+        Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
+        if (resolvedReturnType instanceof Class) {
+            returnType = (Class<?>) resolvedReturnType;
+            if (returnType.isArray()) {
+                returnType = returnType.getComponentType();
+            }
+            // gcode issue #508
+            if (void.class.equals(returnType)) {
+                ResultType rt = method.getAnnotation(ResultType.class);
+                if (rt != null) {
+                    returnType = rt.value();
+                }
+            }
+        } else if (resolvedReturnType instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType;
+            Class<?> rawType = (Class<?>) parameterizedType.getRawType();
+            if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) {
+                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+                if (actualTypeArguments != null && actualTypeArguments.length == 1) {
+                    Type returnTypeParameter = actualTypeArguments[0];
+                    if (returnTypeParameter instanceof Class<?>) {
+                        returnType = (Class<?>) returnTypeParameter;
+                    } else if (returnTypeParameter instanceof ParameterizedType) {
+                        // (gcode issue #443) actual type can be a also a
+                        // parameterized type
+                        returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
+                    } else if (returnTypeParameter instanceof GenericArrayType) {
+                        Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
+                        // (gcode issue #525) support List<byte[]>
+                        returnType = Array.newInstance(componentType, 0).getClass();
+                    }
+                }
+            } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
+                // (gcode issue 504) Do not look into Maps if there is not
+                // MapKey annotation
+                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+                if (actualTypeArguments != null && actualTypeArguments.length == 2) {
+                    Type returnTypeParameter = actualTypeArguments[1];
+                    if (returnTypeParameter instanceof Class<?>) {
+                        returnType = (Class<?>) returnTypeParameter;
+                    } else if (returnTypeParameter instanceof ParameterizedType) {
+                        // (gcode issue 443) actual type can be a also a
+                        // parameterized type
+                        returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
+                    }
+                }
+            }
+        }
+        return returnType;
+    }
+
+    private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
+        try {
+            Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
+            Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
+            if (sqlAnnotationType != null) {
+                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);
+                return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
+            } else if (sqlProviderAnnotationType != null) {
+                Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
+                return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
+            }
+            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(" ");
+        }
+        return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
+    }
+
+    private SqlCommandType getSqlCommandType(Method method) {
+        Class<? extends Annotation> type = getSqlAnnotationType(method);
+        if (type == null) {
+            type = getSqlProviderAnnotationType(method);
+            if (type == null) {
+                return SqlCommandType.UNKNOWN;
+            }
+            if (type == SelectProvider.class) {
+                type = Select.class;
+            } else if (type == InsertProvider.class) {
+                type = Insert.class;
+            } else if (type == UpdateProvider.class) {
+                type = Update.class;
+            } else if (type == DeleteProvider.class) {
+                type = Delete.class;
+            }
+        }
+        return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
+    }
+
+    private Class<? extends Annotation> getSqlAnnotationType(Method method) {
+        return chooseAnnotationType(method, sqlAnnotationTypes);
+    }
+
+    private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
+        return chooseAnnotationType(method, sqlProviderAnnotationTypes);
+    }
+
+    private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
+        for (Class<? extends Annotation> type : types) {
+            Annotation annotation = method.getAnnotation(type);
+            if (annotation != null) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
+        for (Result result : results) {
+            List<ResultFlag> flags = new ArrayList<ResultFlag>();
+            if (result.id()) {
+                flags.add(ResultFlag.ID);
+            }
+            @SuppressWarnings("unchecked")
+            Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) ((result.typeHandler() == UnknownTypeHandler.class) ? null
+                    : result.typeHandler());
+            ResultMapping resultMapping = assistant.buildResultMapping(resultType, nullOrEmpty(result.property()),
+                    nullOrEmpty(result.column()), result.javaType() == void.class ? null : result.javaType(),
+                    result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
+                    hasNestedSelect(result) ? nestedSelectId(result) : null, null, null, null, typeHandler, flags, null, null,
+                    isLazy(result));
+            resultMappings.add(resultMapping);
+        }
+    }
+
+    private String nestedSelectId(Result result) {
+        String nestedSelect = result.one().select();
+        if (nestedSelect.length() < 1) {
+            nestedSelect = result.many().select();
+        }
+        if (!nestedSelect.contains(".")) {
+            nestedSelect = type.getName() + "." + nestedSelect;
+        }
+        return nestedSelect;
+    }
+
+    private boolean isLazy(Result result) {
+        boolean isLazy = configuration.isLazyLoadingEnabled();
+        if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
+            isLazy = (result.one().fetchType() == FetchType.LAZY);
+        } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
+            isLazy = (result.many().fetchType() == FetchType.LAZY);
+        }
+        return isLazy;
+    }
+
+    private boolean hasNestedSelect(Result result) {
+        if (result.one().select().length() > 0 && result.many().select().length() > 0) {
+            throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
+        }
+        return result.one().select().length() > 0 || result.many().select().length() > 0;
+    }
+
+    private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
+        for (Arg arg : args) {
+            List<ResultFlag> flags = new ArrayList<ResultFlag>();
+            flags.add(ResultFlag.CONSTRUCTOR);
+            if (arg.id()) {
+                flags.add(ResultFlag.ID);
+            }
+            @SuppressWarnings("unchecked")
+            Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) (arg.typeHandler() == UnknownTypeHandler.class ? null
+                    : arg.typeHandler());
+            ResultMapping resultMapping = assistant.buildResultMapping(resultType, null, nullOrEmpty(arg.column()),
+                    arg.javaType() == void.class ? null : arg.javaType(),
+                    arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(), nullOrEmpty(arg.select()),
+                    nullOrEmpty(arg.resultMap()), null, null, typeHandler, flags, null, null, false);
+            resultMappings.add(resultMapping);
         }
-	}
-
-	private void parsePendingMethods() {
-		Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
-		synchronized (incompleteMethods) {
-			Iterator<MethodResolver> iter = incompleteMethods.iterator();
-			while (iter.hasNext()) {
-				try {
-					iter.next().resolve();
-					iter.remove();
-				} catch (IncompleteElementException e) {
-					// This method is still missing a resource
-				}
-			}
-		}
-	}
-
-	private void loadXmlResource() {
-		// Spring may not know the real resource name so we check a flag
-		// to prevent loading again a resource twice
-		// this flag is set at XMLMapperBuilder#bindMapperForNamespace
-		if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
-			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 xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource,
-						configuration.getSqlFragments(), type.getName());
-				xmlParser.parse();
-			}
-		}
-	}
-
-	private void parseCache() {
-		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();
-			assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size,
-					cacheDomain.readWrite(), cacheDomain.blocking(), null);
-		}
-	}
-
-	private void parseCacheRef() {
-		CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
-		if (cacheDomainRef != null) {
-			assistant.useCacheRef(cacheDomainRef.value().getName());
-		}
-	}
-
-	private String parseResultMap(Method method) {
-		Class<?> returnType = getReturnType(method);
-		ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);
-		Results results = method.getAnnotation(Results.class);
-		TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
-		String resultMapId = generateResultMapName(method);
-		applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);
-		return resultMapId;
-	}
-
-	private String generateResultMapName(Method method) {
-		Results results = method.getAnnotation(Results.class);
-		if (results != null && !results.id().isEmpty()) {
-			return type.getName() + "." + results.id();
-		}
-		StringBuilder suffix = new StringBuilder();
-		for (Class<?> c : method.getParameterTypes()) {
-			suffix.append("-");
-			suffix.append(c.getSimpleName());
-		}
-		if (suffix.length() < 1) {
-			suffix.append("-void");
-		}
-		return type.getName() + "." + method.getName() + suffix;
-	}
-
-	private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results,
-			TypeDiscriminator discriminator) {
-		List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
-		applyConstructorArgs(args, returnType, resultMappings);
-		applyResults(results, returnType, resultMappings);
-		Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
-		// TODO add AutoMappingBehaviour
-		assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
-		createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
-	}
-
-	private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
-		if (discriminator != null) {
-			for (Case c : discriminator.cases()) {
-				String caseResultMapId = resultMapId + "-" + c.value();
-				List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
-				// issue #136
-				applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
-				applyResults(c.results(), resultType, resultMappings);
-				// TODO add AutoMappingBehaviour
-				assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
-			}
-		}
-	}
-
-	private Discriminator applyDiscriminator(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
-		if (discriminator != null) {
-			String column = discriminator.column();
-			Class<?> javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType();
-			JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
-			@SuppressWarnings("unchecked")
-			Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) (discriminator.typeHandler() == UnknownTypeHandler.class ? null
-					: discriminator.typeHandler());
-			Case[] cases = discriminator.cases();
-			Map<String, String> discriminatorMap = new HashMap<String, String>();
-			for (Case c : cases) {
-				String value = c.value();
-				String caseResultMapId = resultMapId + "-" + value;
-				discriminatorMap.put(value, caseResultMapId);
-			}
-			return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
-		}
-		return null;
-	}
-
-	void parseStatement(Method method) {
-		Class<?> parameterTypeClass = getParameterType(method);
-		LanguageDriver languageDriver = getLanguageDriver(method);
-		SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
-		if (sqlSource != null) {
-			Options options = method.getAnnotation(Options.class);
-			final String mappedStatementId = type.getName() + "." + method.getName();
-			Integer fetchSize = null;
-			Integer timeout = null;
-			StatementType statementType = StatementType.PREPARED;
-			ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
-			SqlCommandType sqlCommandType = getSqlCommandType(method);
-			boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
-			boolean flushCache = !isSelect;
-			boolean useCache = isSelect;
-
-			KeyGenerator keyGenerator;
-			String keyProperty = "id";
-			String keyColumn = null;
-			if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
-				// first check for SelectKey annotation - that overrides
-				// everything else
-				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() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
-				} else {
-					keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
-					keyProperty = options.keyProperty();
-					keyColumn = options.keyColumn();
-				}
-			} else {
-				keyGenerator = new NoKeyGenerator();
-			}
-
-			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();
-				resultSetType = options.resultSetType();
-			}
-
-			String resultMapId = null;
-			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 LanguageDriver getLanguageDriver(Method method) {
-		Lang lang = method.getAnnotation(Lang.class);
-		Class<?> langClass = null;
-		if (lang != null) {
-			langClass = lang.value();
-		}
-		return assistant.getLanguageDriver(langClass);
-	}
-
-	private Class<?> getParameterType(Method method) {
-		Class<?> parameterType = null;
-		Class<?>[] parameterTypes = method.getParameterTypes();
-		for (Class<?> currentParameterType : parameterTypes) {
-			if (!RowBounds.class.isAssignableFrom(currentParameterType)
-					&& !ResultHandler.class.isAssignableFrom(currentParameterType)) {
-				if (parameterType == null) {
-					parameterType = currentParameterType;
-				} else {
-					// issue #135
-					parameterType = ParamMap.class;
-				}
-			}
-		}
-		return parameterType;
-	}
-
-	private Class<?> getReturnType(Method method) {
-		Class<?> returnType = method.getReturnType();
-		Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type);
-		if (resolvedReturnType instanceof Class) {
-			returnType = (Class<?>) resolvedReturnType;
-			if (returnType.isArray()) {
-				returnType = returnType.getComponentType();
-			}
-			// gcode issue #508
-			if (void.class.equals(returnType)) {
-				ResultType rt = method.getAnnotation(ResultType.class);
-				if (rt != null) {
-					returnType = rt.value();
-				}
-			}
-		} else if (resolvedReturnType instanceof ParameterizedType) {
-			ParameterizedType parameterizedType = (ParameterizedType) resolvedReturnType;
-			Class<?> rawType = (Class<?>) parameterizedType.getRawType();
-			if (Collection.class.isAssignableFrom(rawType) || Cursor.class.isAssignableFrom(rawType)) {
-				Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
-				if (actualTypeArguments != null && actualTypeArguments.length == 1) {
-					Type returnTypeParameter = actualTypeArguments[0];
-					if (returnTypeParameter instanceof Class<?>) {
-						returnType = (Class<?>) returnTypeParameter;
-					} else if (returnTypeParameter instanceof ParameterizedType) {
-						// (gcode issue #443) actual type can be a also a
-						// parameterized type
-						returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
-					} else if (returnTypeParameter instanceof GenericArrayType) {
-						Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
-						// (gcode issue #525) support List<byte[]>
-						returnType = Array.newInstance(componentType, 0).getClass();
-					}
-				}
-			} else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(rawType)) {
-				// (gcode issue 504) Do not look into Maps if there is not
-				// MapKey annotation
-				Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
-				if (actualTypeArguments != null && actualTypeArguments.length == 2) {
-					Type returnTypeParameter = actualTypeArguments[1];
-					if (returnTypeParameter instanceof Class<?>) {
-						returnType = (Class<?>) returnTypeParameter;
-					} else if (returnTypeParameter instanceof ParameterizedType) {
-						// (gcode issue 443) actual type can be a also a
-						// parameterized type
-						returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
-					}
-				}
-			}
-		}
-
-		return returnType;
-	}
-
-	private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
-		try {
-			Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
-			Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
-			if (sqlAnnotationType != null) {
-				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);
-				return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
-			} else if (sqlProviderAnnotationType != null) {
-				Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
-				return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
-			}
-			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(" ");
-		}
-		return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
-	}
-
-	private SqlCommandType getSqlCommandType(Method method) {
-		Class<? extends Annotation> type = getSqlAnnotationType(method);
-
-		if (type == null) {
-			type = getSqlProviderAnnotationType(method);
-
-			if (type == null) {
-				return SqlCommandType.UNKNOWN;
-			}
-
-			if (type == SelectProvider.class) {
-				type = Select.class;
-			} else if (type == InsertProvider.class) {
-				type = Insert.class;
-			} else if (type == UpdateProvider.class) {
-				type = Update.class;
-			} else if (type == DeleteProvider.class) {
-				type = Delete.class;
-			}
-		}
-
-		return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
-	}
-
-	private Class<? extends Annotation> getSqlAnnotationType(Method method) {
-		return chooseAnnotationType(method, sqlAnnotationTypes);
-	}
-
-	private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
-		return chooseAnnotationType(method, sqlProviderAnnotationTypes);
-	}
-
-	private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
-		for (Class<? extends Annotation> type : types) {
-			Annotation annotation = method.getAnnotation(type);
-			if (annotation != null) {
-				return type;
-			}
-		}
-		return null;
-	}
-
-	private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
-		for (Result result : results) {
-			List<ResultFlag> flags = new ArrayList<ResultFlag>();
-			if (result.id()) {
-				flags.add(ResultFlag.ID);
-			}
-			@SuppressWarnings("unchecked")
-			Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) ((result.typeHandler() == UnknownTypeHandler.class) ? null
-					: result.typeHandler());
-			ResultMapping resultMapping = assistant.buildResultMapping(resultType, nullOrEmpty(result.property()),
-					nullOrEmpty(result.column()), result.javaType() == void.class ? null : result.javaType(),
-					result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
-					hasNestedSelect(result) ? nestedSelectId(result) : null, null, null, null, typeHandler, flags, null, null,
-					isLazy(result));
-			resultMappings.add(resultMapping);
-		}
-	}
-
-	private String nestedSelectId(Result result) {
-		String nestedSelect = result.one().select();
-		if (nestedSelect.length() < 1) {
-			nestedSelect = result.many().select();
-		}
-		if (!nestedSelect.contains(".")) {
-			nestedSelect = type.getName() + "." + nestedSelect;
-		}
-		return nestedSelect;
-	}
-
-	private boolean isLazy(Result result) {
-		boolean isLazy = configuration.isLazyLoadingEnabled();
-		if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
-			isLazy = (result.one().fetchType() == FetchType.LAZY);
-		} else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
-			isLazy = (result.many().fetchType() == FetchType.LAZY);
-		}
-		return isLazy;
-	}
-
-	private boolean hasNestedSelect(Result result) {
-		if (result.one().select().length() > 0 && result.many().select().length() > 0) {
-			throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
-		}
-		return result.one().select().length() > 0 || result.many().select().length() > 0;
-	}
-
-	private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
-		for (Arg arg : args) {
-			List<ResultFlag> flags = new ArrayList<ResultFlag>();
-			flags.add(ResultFlag.CONSTRUCTOR);
-			if (arg.id()) {
-				flags.add(ResultFlag.ID);
-			}
-			@SuppressWarnings("unchecked")
-			Class<? extends TypeHandler<?>> typeHandler = (Class<? extends TypeHandler<?>>) (arg.typeHandler() == UnknownTypeHandler.class ? null
-					: arg.typeHandler());
-			ResultMapping resultMapping = assistant.buildResultMapping(resultType, null, nullOrEmpty(arg.column()),
-					arg.javaType() == void.class ? null : arg.javaType(),
-					arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(), nullOrEmpty(arg.select()),
-					nullOrEmpty(arg.resultMap()), null, null, typeHandler, flags, null, null, false);
-			resultMappings.add(resultMapping);
-		}
-	}
-
-	private String nullOrEmpty(String value) {
-		return value == null || value.trim().length() == 0 ? null : value;
-	}
-
-	private Result[] resultsIf(Results results) {
-		return results == null ? new Result[0] : results.value();
-	}
-
-	private Arg[] argsIf(ConstructorArgs args) {
-		return args == null ? new Arg[0] : args.value();
-	}
-
-	private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId,
-			Class<?> parameterTypeClass, LanguageDriver languageDriver) {
-		String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
-		Class<?> resultTypeClass = selectKeyAnnotation.resultType();
-		StatementType statementType = selectKeyAnnotation.statementType();
-		String keyProperty = selectKeyAnnotation.keyProperty();
-		String keyColumn = selectKeyAnnotation.keyColumn();
-		boolean executeBefore = selectKeyAnnotation.before();
-
-		// defaults
-		boolean useCache = false;
-		KeyGenerator keyGenerator = new NoKeyGenerator();
-		Integer fetchSize = null;
-		Integer timeout = null;
-		boolean flushCache = false;
-		String parameterMap = null;
-		String resultMap = null;
-		ResultSetType resultSetTypeEnum = null;
-
-		SqlSource sqlSource = buildSqlSourceFromStrings(selectKeyAnnotation.statement(), parameterTypeClass, languageDriver);
-		SqlCommandType sqlCommandType = SqlCommandType.SELECT;
-
-		assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
-				parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, false, keyGenerator,
-				keyProperty, keyColumn, null, languageDriver, null);
-
-		id = assistant.applyCurrentNamespace(id, false);
-
-		MappedStatement keyStatement = configuration.getMappedStatement(id, false);
-		SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
-		configuration.addKeyGenerator(id, answer);
-		return answer;
-	}
+    }
+
+    private String nullOrEmpty(String value) {
+        return value == null || value.trim().length() == 0 ? null : value;
+    }
+
+    private Result[] resultsIf(Results results) {
+        return results == null ? new Result[0] : results.value();
+    }
+
+    private Arg[] argsIf(ConstructorArgs args) {
+        return args == null ? new Arg[0] : args.value();
+    }
+
+    private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId,
+                                                   Class<?> parameterTypeClass, LanguageDriver languageDriver) {
+        String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
+        Class<?> resultTypeClass = selectKeyAnnotation.resultType();
+        StatementType statementType = selectKeyAnnotation.statementType();
+        String keyProperty = selectKeyAnnotation.keyProperty();
+        String keyColumn = selectKeyAnnotation.keyColumn();
+        boolean executeBefore = selectKeyAnnotation.before();
+        // defaults
+        boolean useCache = false;
+        KeyGenerator keyGenerator = new NoKeyGenerator();
+        Integer fetchSize = null;
+        Integer timeout = null;
+        boolean flushCache = false;
+        String parameterMap = null;
+        String resultMap = null;
+        ResultSetType resultSetTypeEnum = null;
+        SqlSource sqlSource = buildSqlSourceFromStrings(selectKeyAnnotation.statement(), parameterTypeClass, languageDriver);
+        SqlCommandType sqlCommandType = SqlCommandType.SELECT;
+        assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
+                parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, false, keyGenerator,
+                keyProperty, keyColumn, null, languageDriver, null);
+        id = assistant.applyCurrentNamespace(id, false);
+        MappedStatement keyStatement = configuration.getMappedStatement(id, false);
+        SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
+        configuration.addKeyGenerator(id, answer);
+        return answer;
+    }
 
 }

+ 4 - 1
mybatis-plus/src/main/java/com/baomidou/mybatisplus/MybatisPulsMapperRegistry.java

@@ -63,7 +63,10 @@ public class MybatisPulsMapperRegistry extends MapperRegistry {
 	public <T> void addMapper(Class<T> type) {
 		if (type.isInterface()) {
 			if (hasMapper(type)) {
-				throw new BindingException("Type " + type + " is already known to the MybatisPlusMapperRegistry.");
+				// 如果之前注入 直接返回
+				return;
+				// throw new BindingException("Type " + type +
+				// " is already known to the MybatisPlusMapperRegistry.");
 			}
 			boolean loadCompleted = false;
 			try {

+ 8 - 10
mybatis-plus/src/main/java/com/baomidou/mybatisplus/MybatisXMLConfigBuilder.java

@@ -15,15 +15,8 @@
  */
 package com.baomidou.mybatisplus;
 
-import java.io.InputStream;
-import java.io.Reader;
-import java.util.Properties;
-
-import javax.sql.DataSource;
-
 import org.apache.ibatis.builder.BaseBuilder;
 import org.apache.ibatis.builder.BuilderException;
-import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
 import org.apache.ibatis.datasource.DataSourceFactory;
 import org.apache.ibatis.executor.ErrorContext;
@@ -49,12 +42,17 @@ import org.apache.ibatis.session.LocalCacheScope;
 import org.apache.ibatis.transaction.TransactionFactory;
 import org.apache.ibatis.type.JdbcType;
 
+import javax.sql.DataSource;
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.Properties;
+
 /**
  * <p>
  * Copy from XMLConfigBuilder in Mybatis and replace default Configuration class
  * by MybatisConfiguration class
  * </p>
- * 
+ *
  * @author hubin
  * @Date 2016-04-20
  */
@@ -379,14 +377,14 @@ public class MybatisXMLConfigBuilder extends BaseBuilder {
 						ErrorContext.instance().resource(resource);
 						InputStream inputStream = Resources.getResourceAsStream(resource);
 						//TODO
-						XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
+						MybatisXMLMapperBuilder mapperParser = new MybatisXMLMapperBuilder(inputStream, configuration, resource,
 								configuration.getSqlFragments());
 						mapperParser.parse();
 					} else if (resource == null && url != null && mapperClass == null) {
 						ErrorContext.instance().resource(url);
 						InputStream inputStream = Resources.getUrlAsStream(url);
 						//TODO
-						XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
+						MybatisXMLMapperBuilder mapperParser = new MybatisXMLMapperBuilder(inputStream, configuration, url,
 								configuration.getSqlFragments());
 						mapperParser.parse();
 					} else if (resource == null && url == null && mapperClass != null) {

+ 447 - 0
mybatis-plus/src/main/java/com/baomidou/mybatisplus/MybatisXMLMapperBuilder.java

@@ -0,0 +1,447 @@
+/**
+ * Copyright (c) 2011-2014, hubin (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.baomidou.mybatisplus;
+
+import com.baomidou.mybatisplus.mapper.BaseMapper;
+import org.apache.ibatis.builder.BaseBuilder;
+import org.apache.ibatis.builder.BuilderException;
+import org.apache.ibatis.builder.CacheRefResolver;
+import org.apache.ibatis.builder.IncompleteElementException;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
+import org.apache.ibatis.builder.ResultMapResolver;
+import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
+import org.apache.ibatis.builder.xml.XMLStatementBuilder;
+import org.apache.ibatis.cache.Cache;
+import org.apache.ibatis.executor.ErrorContext;
+import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.mapping.Discriminator;
+import org.apache.ibatis.mapping.ParameterMapping;
+import org.apache.ibatis.mapping.ParameterMode;
+import org.apache.ibatis.mapping.ResultFlag;
+import org.apache.ibatis.mapping.ResultMap;
+import org.apache.ibatis.mapping.ResultMapping;
+import org.apache.ibatis.parsing.XNode;
+import org.apache.ibatis.parsing.XPathParser;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * <p>
+ * org.apache.ibatis.builder.xml.XMLMapperBuilder
+ * </p>
+ * <p>
+ * injector curdSql
+ * </p>
+ * 
+ * @author Clinton Begin hubin
+ * @Date 2016-06-13
+ */
+public class MybatisXMLMapperBuilder extends BaseBuilder {
+
+	private XPathParser parser;
+	private MapperBuilderAssistant builderAssistant;
+	private Map<String, XNode> sqlFragments;
+	private String resource;
+
+	@Deprecated
+	public MybatisXMLMapperBuilder(Reader reader, Configuration configuration, String resource,
+			Map<String, XNode> sqlFragments, String namespace) {
+		this(reader, configuration, resource, sqlFragments);
+		this.builderAssistant.setCurrentNamespace(namespace);
+	}
+
+	@Deprecated
+	public MybatisXMLMapperBuilder(Reader reader, Configuration configuration, String resource,
+			Map<String, XNode> sqlFragments) {
+		this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
+				resource, sqlFragments);
+	}
+
+	public MybatisXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
+			Map<String, XNode> sqlFragments, String namespace) {
+		this(inputStream, configuration, resource, sqlFragments);
+		this.builderAssistant.setCurrentNamespace(namespace);
+	}
+
+	public MybatisXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
+			Map<String, XNode> sqlFragments) {
+		this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
+				configuration, resource, sqlFragments);
+	}
+
+	private MybatisXMLMapperBuilder(XPathParser parser, Configuration configuration, String resource,
+			Map<String, XNode> sqlFragments) {
+		super(configuration);
+		this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
+		this.parser = parser;
+		this.sqlFragments = sqlFragments;
+		this.resource = resource;
+	}
+
+	public void parse() {
+		if (!configuration.isResourceLoaded(resource)) {
+			configurationElement(parser.evalNode("/mapper"));
+			configuration.addLoadedResource(resource);
+			bindMapperForNamespace();
+		}
+
+		parsePendingResultMaps();
+		parsePendingChacheRefs();
+		parsePendingStatements();
+	}
+
+	public XNode getSqlFragment(String refid) {
+		return sqlFragments.get(refid);
+	}
+
+	private void configurationElement(XNode context) {
+		try {
+			String namespace = context.getStringAttribute("namespace");
+			if (namespace == null || namespace.equals("")) {
+				throw new BuilderException("Mapper's namespace cannot be empty");
+			}
+			builderAssistant.setCurrentNamespace(namespace);
+			cacheRefElement(context.evalNode("cache-ref"));
+			cacheElement(context.evalNode("cache"));
+			parameterMapElement(context.evalNodes("/mapper/parameterMap"));
+			resultMapElements(context.evalNodes("/mapper/resultMap"));
+			sqlElement(context.evalNodes("/mapper/sql"));
+			buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
+		} catch (Exception e) {
+			throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
+		}
+	}
+
+	private void buildStatementFromContext(List<XNode> list) {
+		if (configuration.getDatabaseId() != null) {
+			buildStatementFromContext(list, configuration.getDatabaseId());
+		}
+		buildStatementFromContext(list, null);
+	}
+
+	private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
+		for (XNode context : list) {
+			final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant,
+					context, requiredDatabaseId);
+			try {
+				statementParser.parseStatementNode();
+			} catch (IncompleteElementException e) {
+				configuration.addIncompleteStatement(statementParser);
+			}
+		}
+	}
+
+	private void parsePendingResultMaps() {
+		Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
+		synchronized (incompleteResultMaps) {
+			Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
+			while (iter.hasNext()) {
+				try {
+					iter.next().resolve();
+					iter.remove();
+				} catch (IncompleteElementException e) {
+					// ResultMap is still missing a resource...
+				}
+			}
+		}
+	}
+
+	private void parsePendingChacheRefs() {
+		Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
+		synchronized (incompleteCacheRefs) {
+			Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
+			while (iter.hasNext()) {
+				try {
+					iter.next().resolveCacheRef();
+					iter.remove();
+				} catch (IncompleteElementException e) {
+					// Cache ref is still missing a resource...
+				}
+			}
+		}
+	}
+
+	private void parsePendingStatements() {
+		Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
+		synchronized (incompleteStatements) {
+			Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
+			while (iter.hasNext()) {
+				try {
+					iter.next().parseStatementNode();
+					iter.remove();
+				} catch (IncompleteElementException e) {
+					// Statement is still missing a resource...
+				}
+			}
+		}
+	}
+
+	private void cacheRefElement(XNode context) {
+		if (context != null) {
+			configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
+			CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant,
+					context.getStringAttribute("namespace"));
+			try {
+				cacheRefResolver.resolveCacheRef();
+			} catch (IncompleteElementException e) {
+				configuration.addIncompleteCacheRef(cacheRefResolver);
+			}
+		}
+	}
+
+	private void cacheElement(XNode context) throws Exception {
+		if (context != null) {
+			String type = context.getStringAttribute("type", "PERPETUAL");
+			Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
+			String eviction = context.getStringAttribute("eviction", "LRU");
+			Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
+			Long flushInterval = context.getLongAttribute("flushInterval");
+			Integer size = context.getIntAttribute("size");
+			boolean readWrite = !context.getBooleanAttribute("readOnly", false);
+			boolean blocking = context.getBooleanAttribute("blocking", false);
+			Properties props = context.getChildrenAsProperties();
+			builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
+		}
+	}
+
+	private void parameterMapElement(List<XNode> list) throws Exception {
+		for (XNode parameterMapNode : list) {
+			String id = parameterMapNode.getStringAttribute("id");
+			String type = parameterMapNode.getStringAttribute("type");
+			Class<?> parameterClass = resolveClass(type);
+			List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
+			List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
+			for (XNode parameterNode : parameterNodes) {
+				String property = parameterNode.getStringAttribute("property");
+				String javaType = parameterNode.getStringAttribute("javaType");
+				String jdbcType = parameterNode.getStringAttribute("jdbcType");
+				String resultMap = parameterNode.getStringAttribute("resultMap");
+				String mode = parameterNode.getStringAttribute("mode");
+				String typeHandler = parameterNode.getStringAttribute("typeHandler");
+				Integer numericScale = parameterNode.getIntAttribute("numericScale");
+				ParameterMode modeEnum = resolveParameterMode(mode);
+				Class<?> javaTypeClass = resolveClass(javaType);
+				JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
+				@SuppressWarnings("unchecked")
+				Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(
+						typeHandler);
+				ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property,
+						javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
+				parameterMappings.add(parameterMapping);
+			}
+			builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
+		}
+	}
+
+	private void resultMapElements(List<XNode> list) throws Exception {
+		for (XNode resultMapNode : list) {
+			try {
+				resultMapElement(resultMapNode);
+			} catch (IncompleteElementException e) {
+				// ignore, it will be retried
+			}
+		}
+	}
+
+	private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
+		return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
+	}
+
+	private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings)
+			throws Exception {
+		ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
+		String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
+		String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
+				resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
+		String extend = resultMapNode.getStringAttribute("extends");
+		Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
+		Class<?> typeClass = resolveClass(type);
+		Discriminator discriminator = null;
+		List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
+		resultMappings.addAll(additionalResultMappings);
+		List<XNode> resultChildren = resultMapNode.getChildren();
+		for (XNode resultChild : resultChildren) {
+			if ("constructor".equals(resultChild.getName())) {
+				processConstructorElement(resultChild, typeClass, resultMappings);
+			} else if ("discriminator".equals(resultChild.getName())) {
+				discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
+			} else {
+				List<ResultFlag> flags = new ArrayList<ResultFlag>();
+				if ("id".equals(resultChild.getName())) {
+					flags.add(ResultFlag.ID);
+				}
+				resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
+			}
+		}
+		ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend,
+				discriminator, resultMappings, autoMapping);
+		try {
+			return resultMapResolver.resolve();
+		} catch (IncompleteElementException e) {
+			configuration.addIncompleteResultMap(resultMapResolver);
+			throw e;
+		}
+	}
+
+	private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings)
+			throws Exception {
+		List<XNode> argChildren = resultChild.getChildren();
+		for (XNode argChild : argChildren) {
+			List<ResultFlag> flags = new ArrayList<ResultFlag>();
+			flags.add(ResultFlag.CONSTRUCTOR);
+			if ("idArg".equals(argChild.getName())) {
+				flags.add(ResultFlag.ID);
+			}
+			resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
+		}
+	}
+
+	private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType,
+			List<ResultMapping> resultMappings) throws Exception {
+		String column = context.getStringAttribute("column");
+		String javaType = context.getStringAttribute("javaType");
+		String jdbcType = context.getStringAttribute("jdbcType");
+		String typeHandler = context.getStringAttribute("typeHandler");
+		Class<?> javaTypeClass = resolveClass(javaType);
+		@SuppressWarnings("unchecked")
+		Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
+		JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
+		Map<String, String> discriminatorMap = new HashMap<String, String>();
+		for (XNode caseChild : context.getChildren()) {
+			String value = caseChild.getStringAttribute("value");
+			String resultMap = caseChild.getStringAttribute("resultMap",
+					processNestedResultMappings(caseChild, resultMappings));
+			discriminatorMap.put(value, resultMap);
+		}
+		return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass,
+				discriminatorMap);
+	}
+
+	private void sqlElement(List<XNode> list) throws Exception {
+		if (configuration.getDatabaseId() != null) {
+			sqlElement(list, configuration.getDatabaseId());
+		}
+		sqlElement(list, null);
+	}
+
+	private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
+		for (XNode context : list) {
+			String databaseId = context.getStringAttribute("databaseId");
+			String id = context.getStringAttribute("id");
+			id = builderAssistant.applyCurrentNamespace(id, false);
+			if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
+				sqlFragments.put(id, context);
+			}
+		}
+	}
+
+	private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
+		if (requiredDatabaseId != null) {
+			if (!requiredDatabaseId.equals(databaseId)) {
+				return false;
+			}
+		} else {
+			if (databaseId != null) {
+				return false;
+			}
+			// skip this fragment if there is a previous one with a not null
+			// databaseId
+			if (this.sqlFragments.containsKey(id)) {
+				XNode context = this.sqlFragments.get(id);
+				if (context.getStringAttribute("databaseId") != null) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags)
+			throws Exception {
+		String property = context.getStringAttribute("property");
+		String column = context.getStringAttribute("column");
+		String javaType = context.getStringAttribute("javaType");
+		String jdbcType = context.getStringAttribute("jdbcType");
+		String nestedSelect = context.getStringAttribute("select");
+		String nestedResultMap = context.getStringAttribute("resultMap",
+				processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
+		String notNullColumn = context.getStringAttribute("notNullColumn");
+		String columnPrefix = context.getStringAttribute("columnPrefix");
+		String typeHandler = context.getStringAttribute("typeHandler");
+		String resultSet = context.getStringAttribute("resultSet");
+		String foreignColumn = context.getStringAttribute("foreignColumn");
+		boolean lazy = "lazy".equals(
+				context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
+		Class<?> javaTypeClass = resolveClass(javaType);
+		@SuppressWarnings("unchecked")
+		Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
+		JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
+		return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum,
+				nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet,
+				foreignColumn, lazy);
+	}
+
+	private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
+		if ("association".equals(context.getName()) || "collection".equals(context.getName())
+				|| "case".equals(context.getName())) {
+			if (context.getStringAttribute("select") == null) {
+				ResultMap resultMap = resultMapElement(context, resultMappings);
+				return resultMap.getId();
+			}
+		}
+		return null;
+	}
+
+	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)) {
+					// 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);
+				}
+				//TODO 注入 CURD 动态 SQL
+				if (BaseMapper.class.isAssignableFrom(boundType)) {
+					MybatisConfiguration.SQL_INJECTOR.inspectInject(configuration, builderAssistant, boundType);
+				}
+			}
+		}
+	}
+
+}

+ 17 - 0
mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/AutoSqlInjector.java

@@ -37,6 +37,7 @@ import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.logging.Logger;
 
 /**
@@ -58,6 +59,22 @@ public class AutoSqlInjector implements ISqlInjector {
 
 	protected DBType dbType = DBType.MYSQL;
 
+	/**
+	 * CRUD注入后给予标识 注入过后不再注入
+	 *
+	 * @param configuration
+	 * @param builderAssistant
+	 * @param mapperClass
+	 */
+	public void inspectInject(Configuration configuration, MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
+		String className = mapperClass.toString();
+		Set<String> mapperRegistryCache = MybatisConfiguration.MAPPER_REGISTRY_CACHE;
+		if (!mapperRegistryCache.contains(className)) {
+			inject(configuration, builderAssistant, mapperClass);
+			mapperRegistryCache.add(className);
+		}
+	}
+
 	/**
 	 * 注入单点 crudSql
 	 */

+ 10 - 0
mybatis-plus/src/main/java/com/baomidou/mybatisplus/mapper/ISqlInjector.java

@@ -35,4 +35,14 @@ public interface ISqlInjector {
 	 */
 	void inject(Configuration configuration, MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
 
+	/**
+	 * <p>
+	 * 检查SQL是否已经注入
+	 * </p>
+	 * <p>
+	 * ps:注入基本SQL后给予标识 注入过不再注入
+	 * </p>
+	 */
+	void inspectInject(Configuration configuration, MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
+
 }

+ 15 - 9
mybatis-plus/src/main/java/com/baomidou/mybatisplus/spring/MybatisSqlSessionFactoryBean.java

@@ -16,13 +16,13 @@
 package com.baomidou.mybatisplus.spring;
 
 import com.baomidou.mybatisplus.MybatisConfiguration;
+import com.baomidou.mybatisplus.MybatisXMLConfigBuilder;
+import com.baomidou.mybatisplus.MybatisXMLMapperBuilder;
 import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
 import com.baomidou.mybatisplus.mapper.DBType;
 import com.baomidou.mybatisplus.mapper.IMetaObjectHandler;
 import com.baomidou.mybatisplus.mapper.ISqlInjector;
 import com.baomidou.mybatisplus.toolkit.PackageHelper;
-import org.apache.ibatis.builder.xml.XMLConfigBuilder;
-import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.cache.Cache;
 import org.apache.ibatis.executor.ErrorContext;
 import org.apache.ibatis.io.VFS;
@@ -437,7 +437,8 @@ public class MybatisSqlSessionFactoryBean
 
 		Configuration configuration;
 
-		XMLConfigBuilder xmlConfigBuilder = null;
+		// TODO 加载自定义 MybatisXmlConfigBuilder
+		MybatisXMLConfigBuilder xmlConfigBuilder = null;
 		if (this.configuration != null) {
 			configuration = this.configuration;
 			if (configuration.getVariables() == null) {
@@ -446,11 +447,13 @@ public class MybatisSqlSessionFactoryBean
 				configuration.getVariables().putAll(this.configurationProperties);
 			}
 		} else if (this.configLocation != null) {
-			xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
+			xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null,
+					this.configurationProperties);
 			configuration = xmlConfigBuilder.getConfiguration();
 		} else {
 			if (LOGGER.isDebugEnabled()) {
-				LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
+				LOGGER.debug(
+						"Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
 			}
 			// TODO 使用自定义配置
 			configuration = new MybatisConfiguration();
@@ -528,7 +531,8 @@ public class MybatisSqlSessionFactoryBean
 			}
 		}
 
-		if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
+		if (this.databaseIdProvider != null) {// fix #64 set databaseId before
+			// parse mapper xmls
 			try {
 				configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
 			} catch (SQLException e) {
@@ -567,8 +571,10 @@ public class MybatisSqlSessionFactoryBean
 				}
 
 				try {
-					XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
-							configuration, mapperLocation.toString(), configuration.getSqlFragments());
+					// TODO
+					MybatisXMLMapperBuilder xmlMapperBuilder = new MybatisXMLMapperBuilder(
+							mapperLocation.getInputStream(), configuration, mapperLocation.toString(),
+							configuration.getSqlFragments());
 					xmlMapperBuilder.parse();
 				} catch (Exception e) {
 					throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
@@ -624,4 +630,4 @@ public class MybatisSqlSessionFactoryBean
 		}
 	}
 
-}
+}

+ 4 - 5
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/mysql/NoXMLTest.java

@@ -15,14 +15,13 @@
  */
 package com.baomidou.mybatisplus.test.mysql;
 
-import java.io.InputStream;
-import java.util.List;
-
+import com.baomidou.mybatisplus.MybatisSessionFactoryBuilder;
+import com.baomidou.mybatisplus.test.mysql.entity.Test;
 import org.apache.ibatis.session.SqlSession;
 import org.apache.ibatis.session.SqlSessionFactory;
 
-import com.baomidou.mybatisplus.MybatisSessionFactoryBuilder;
-import com.baomidou.mybatisplus.test.mysql.entity.Test;
+import java.io.InputStream;
+import java.util.List;
 
 /**
  * <p>

+ 3 - 3
mybatis-plus/src/test/resources/mysql-config.xml

@@ -82,7 +82,7 @@
 				<property name="driver" value="com.mysql.jdbc.Driver" />
 				<property name="url" value="jdbc:mysql://localhost:3306/mybatis-plus" />
 				<property name="username" value="root" />
-				<property name="password" value="123456" />
+				<property name="password" value="521" />
 			<!-- 
 				<property name="driver" value="${jdbc.driver}" />
 				<property name="url" value="${jdbc.url}" />
@@ -103,10 +103,10 @@
      | package  指向可以找到Mapper接口的包名
      | -->
     <mappers>
+        <mapper resource="mysql/UserMapper.xml"/>
+        <mapper resource="mysql/RoleMapper.xml"/>
         <mapper class="com.baomidou.mybatisplus.test.mysql.UserMapper" />
         <mapper class="com.baomidou.mybatisplus.test.mysql.RoleMapper" />
         <mapper class="com.baomidou.mybatisplus.test.mysql.TestMapper" />
-        <mapper resource="mysql/UserMapper.xml"/>
-        <mapper resource="mysql/RoleMapper.xml"/>
     </mappers>
 </configuration>