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方法。