Mybatis源碼學習(三)基於@Mapper注解


在上面的例子中,我們使用xml加載Mapper文件,在這一篇博客中,我們使用@Mapper注解加載sql映射

1 示例

我們修改Mybatis源碼學習(一)中的代碼,紅色為修改部分:

MyBatisMain.java

public class MybatisMain {
  public static void main(String[] args) throws IOException
  {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    Blog blog = blogMapper.query(101);
//    Blog blog = sqlSession.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
    System.out.println(blog.getContext());
  }
}

新增接口BlogMapper.java

@Mapper
public interface BlogMapper {
  @Select("select * from blog where id=#{id}")
  Blog query(Integer id);
}

mybatis-config.xml

<configuration>
    <properties resource="jdbc.properties"/>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--<mapper resource="mapper/BlogMapper.xml"/>-->
        <mapper class="org.apache.ibatis.demo.mapper.BlogMapper"/>
    </mappers>
</configuration>

 

2 分析

2.1 SqlSessionFactory

在 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 中,build方法中經過若干步驟,執行如下方法

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

當使用注解方式時,執行如下分支,configuration中添加Mapper接口對應的Class。

else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
}

 

2.2 通過BlogMapper接口查詢

BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = blogMapper.query(101);

第一行代碼SqlSession的getMapper方法

  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

Configuration的getMapper方法

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

MapperRegistry的getMapper方法

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperProxyFactory的newInstance方法

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

這里使用了JDK的Proxy動態代理方式,Proxy.newProxyInstance接收3個參數,第一個參數是ClassLoader,第二個參數是被代理接口BlogMapper,第三個參數是實現了 InvocationHandler 接口的MapperProxy類的實例。(這是Proxy代理的規范)。

 

總而言之,當我們使用@Mapper注解方式時,Mybatis幫助我們使用動態代理的方式代理Mapper接口,來實現接口中所聲明的查詢方法。 Blog blog = blogMapper.query(101); 執行時實際上是調用的代理對象mapperProxy中的invoke方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM