带你了解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