帶你瞭解Mybatis註解式工做原理

Mybatis編寫sql有兩種方式,即經過xml和註解,我我的比較喜歡xml配置,可是註解仍是要了解下的。而且Mybatis中xml優先於註解加載,也就是若是DAO接口中的方法有對應的xml配置,再加入註解會拋異常,若是兩個都沒配置,在調用DAO方法時再拋異常。java

源碼分析
1、XML中sql轉MappedStatement

Mybatis會把編寫的sql語句信息封裝成一個MappedStatement對象,加載xml中sql信息從
XMLMapperBuilder#buildStatementFromContext()開始,一路調用到MapperBuilderAssistant#addMappedStatement完成添加,而加載註解最終也會經過MapperBuilderAssistant類。其中 this.configuration.addMappedStatement(statement);就是最終添加到配置類中的map集合中,先無論。
sql

2、註解轉MappedStatement

那麼開始加載註解從什麼地方開始呢?
一樣是從XMLMapperBuilder類中,在bindMapperForNamespace()方法下開始,完成XML加載以後被調用。
加載註解經過代碼跟蹤一直在MapperRegistry下的addMapper中。此時參數是DAO的class,首先判斷是否是接口,以及是否被添加過了。其次,開始new 一個MapperAnnotationBuilder對象開始處理接口中方法上的註解。mybatis

public <T> void addMapper(Class<T> type) {
      //是否是接口
     if (type.isInterface()) {
         //是否已被添加
         if (this.hasMapper(type)) {
             throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
         }
         boolean loadCompleted = false;
         try {
             //添加
             this.knownMappers.put(type, new MapperProxyFactory(type));
             //開始註解解析
             MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
             parser.parse();
             loadCompleted = true;
         } finally {
             if (!loadCompleted) {
                 this.knownMappers.remove(type);
             }
         }
     }
 }

MapperAnnotationBuilder的parseStatement方法中處理每個DAO層方法上的註解,其中getSqlSourceFromAnnotations返回一個SqlSource對象,爲空則方法上不存在註解信息,什麼也不作,不爲空最終生成各類須要的信息調用MapperBuilderAssistant下的addMappedStatement方法試圖添加到Configuration中的mappedStatements集合中。app

void parseStatement(Method method) {
     Class<?> parameterTypeClass = this.getParameterType(method);
     LanguageDriver languageDriver = this.getLanguageDriver(method);
     SqlSource sqlSource = this.getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
     if (sqlSource != null) {
         Options options = (Options)method.getAnnotation(Options.class);
         String mappedStatementId = this.type.getName() + "." + method.getName();
         Integer fetchSize = null;
         Integer timeout = null;
         StatementType statementType = StatementType.PREPARED;
         ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
         SqlCommandType sqlCommandType = this.getSqlCommandType(method);
         boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
         boolean flushCache = !isSelect;
         boolean useCache = isSelect;
         String keyProperty = "id";
         String keyColumn = null;
         //-----此處省略不知道多少行代碼

那爲何要說試圖添加呢?由於最終存放MappedStatement的是一個Map,Mybatis本身實現了個靜態內部類StrictMap繼承HashMap,在put中判斷了有沒有相同的key,若是相同則拋出異常,也就是間接讓你xml和註解二選一。ide

3、sqlProviderAnnotation

Mybatis還有另外4個註解,也就是SelectProvider、InsertProvider、UpdateProvider、DeleteProvider,
getSqlSourceFromAnnotations中能夠看出,原來的Insert、Select、Update、Delete和SelectProvider、InsertProvider、UpdateProvider、DeleteProvider只能二選一。源碼分析

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
        //獲取方法上Insert、Select、Update、Delete中其中一個註解
        Class<? extends Annotation> sqlAnnotationType = this.getSqlAnnotationType(method);
        //獲取SelectProvider、InsertProvider、UpdateProvider、DeleteProvider中其中一個註解
        Class<? extends Annotation> sqlProviderAnnotationType = this.getSqlProviderAnnotationType(method);
        Annotation sqlProviderAnnotation;
        if (sqlAnnotationType != null) {
            if (sqlProviderAnnotationType != null) {
            //若是兩種註解都存在,則拋出異常
                throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
            } else {
                
                sqlProviderAnnotation = method.getAnnotation(sqlAnnotationType);
                //獲取值,也就是sql語句
                String[] strings = (String[])((String[])sqlProviderAnnotation.getClass().getMethod("value").invoke(sqlProviderAnnotation));                
                //生成SqlSource對象
                return this.buildSqlSourceFromStrings(strings, parameterType, languageDriver);
            }
        } else if (sqlProviderAnnotationType != null) {
            sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
            //ProviderSqlSource實現了SqlSource
            return new ProviderSqlSource(this.assistant.getConfiguration(), sqlProviderAnnotation, this.type, method);
        } else {
            return null;
        }
    } catch (Exception var8) {
        throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + var8, var8);
    }
}
4、SQL ProviderAnnotation的使用
public interface IUserDao {
     @ResultMap({"BaseResultMap"})
     @SelectProvider(type = SelectSql.class,method = "createSelectSql")
     List<UserEntity> select();
     
     class SelectSql{
          public String createSelectSql(){
               return new SQL(){{
                    SELECT("*");
                    FROM("tb_user");
               }}.toString();
          }
     }
}

測試代碼測試

public static void main( String[] args )
    {

        String resource = "mybatis-config.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession2 = build.openSession();
            IUserDao mapper = sqlSession2.getMapper(IUserDao.class);
            System.out.println(mapper.select());

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

end....fetch

相關文章
相關標籤/搜索