Mybatis源码分析之(三)mapper接口底层原理(为什么不用写方法体就能访问到数据库)

x33g5p2x  于2022-01-13 转载在 其他  
字(8.4k)|赞(0)|评价(0)|浏览(333)

mybatis是怎么拿sqlSession

在 上一篇的时候,我们的SqlSessionFactoryBuilder已经从xml文件中解析出了Configuration并且返回了sessionFactory。

然后我们要从session;中拿到sqlSession

  1. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  2. private final Configuration configuration;
  3. @Override
  4. public SqlSession openSession() {
  5. //默认情况下ExecutorType是ExecutorType.SIMPLE类型的
  6. return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  7. }
  8. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  9. Transaction tx = null;
  10. try {
  11. //获取配置的环境信息
  12. final Environment environment = configuration.getEnvironment();
  13. //获取environment中的TransactionFactory
  14. final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  15. //生成Transaction
  16. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  17. //生成Executor(重要,之后的查询都得靠它)
  18. final Executor executor = configuration.newExecutor(tx, execType);
  19. //返回DefaultSqlSession
  20. return new DefaultSqlSession(configuration, executor, autoCommit);
  21. } catch (Exception e) {
  22. closeTransaction(tx); // may have fetched a connection so lets call close()
  23. throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
  24. } finally {
  25. ErrorContext.instance().reset();
  26. }
  27. }
  28. }
  29. //DefaultSqlSession类的组成,其实新建的时候就只是把他的字段赋值而已
  30. public class DefaultSqlSession implements SqlSession {
  31. private Configuration configuration;
  32. private Executor executor;
  33. private boolean autoCommit;
  34. private boolean dirty;
  35. private List<Cursor<?>> cursorList;
  36. }
  37. public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
  38. this.configuration = configuration;
  39. this.executor = executor;
  40. this.dirty = false;
  41. this.autoCommit = autoCommit;
  42. }

上面分析的是到拿到sqlSession为止,重点其实不是在上面这里,因为到上面为止,其实主要的功能只是将配置的信息解析成我们要的类,然后进行初始化赋值。

Mapper的实现原理

下面我们从SqlSession中拿到mapper,并执行方法其实才是,你感觉到mybatis框架开始帮我们做事的开始。

  1. public static void main(String[] args) {
  2. //拿到SqlSession
  3. SqlSession sqlsession = MybatisUtil.getSqlsession();
  4. //拿mapper
  5. TDemoMapper mapper = sqlsession.getMapper(TDemoMapper.class);
  6. //调用mapper的方法
  7. List<TDemo> all = mapper.getAll();
  8. for (TDemo item : all)
  9. System.out.println(item);
  10. }

因为我们在项目中的TDemoMapper只是一个接口,并没有实现这个接口方法,但是为什么我们在调用这个接口方法的时候就可以得到返回结果呢?mybatis究竟做了什么?

首先我们回到之前解析Mapper的语句

  1. mapperElement(root.evalNode("mappers"));
  2. private void mapperElement(XNode parent) throws Exception {
  3. if (parent != null) {
  4. for (XNode child : parent.getChildren()) {
  5. if ("package".equals(child.getName())) {
  6. String mapperPackage = child.getStringAttribute("name");
  7. configuration.addMappers(mapperPackage);
  8. } else {
  9. String resource = child.getStringAttribute("resource");
  10. String url = child.getStringAttribute("url");
  11. String mapperClass = child.getStringAttribute("class");
  12. if (resource != null && url == null && mapperClass == null) { //根据resource解析
  13. ErrorContext.instance().resource(resource);
  14. InputStream inputStream = Resources.getResourceAsStream(resource);
  15. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  16. mapperParser.parse();
  17. } else if (resource == null && url != null && mapperClass == null) {//根据url 解析
  18. ErrorContext.instance().resource(url);
  19. InputStream inputStream = Resources.getUrlAsStream(url);
  20. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  21. mapperParser.parse();
  22. } else if (resource == null && url == null && mapperClass != null) {//根据mapperClass解析
  23. //首先通过mapperClass的路径,生成mapperClass的接口类
  24. Class<?> mapperInterface = Resources.classForName(mapperClass);
  25. //降mapperClass加入到configuration中去
  26. configuration.addMapper(mapperInterface);
  27. } else {
  28. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  29. }
  30. }
  31. }
  32. }
  33. }
  34. //Configuration类下
  35. public <T> void addMapper(Class<T> type) {
  36. mapperRegistry.addMapper(type);
  37. }
  38. //MapperRegistry类下
  39. public class MapperRegistry {
  40. private final Configuration config;
  41. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
  42. //最终调用这个方法
  43. public <T> void addMapper(Class<T> type) {
  44. if (type.isInterface()) {
  45. if (hasMapper(type)) {
  46. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  47. }
  48. boolean loadCompleted = false;
  49. try {
  50. //将接口包装成MapperProxyFactory类放入knownMappers中(knownMappers就是存放我们的mapper接口的)
  51. knownMappers.put(type, new MapperProxyFactory<T>(type));
  52. // It's important that the type is added before the parser is run
  53. // otherwise the binding may automatically be attempted by the
  54. // mapper parser. If the type is already known, it won't try.
  55. //通过这个builder来解析mapper的statement。(把mapper和mapper.xml文件相关联,方法名与xml中的id相关联,为了之后调用的时候能找到的语句)
  56. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  57. //开始解析
  58. parser.parse();
  59. loadCompleted = true;
  60. } finally {
  61. if (!loadCompleted) {
  62. knownMappers.remove(type);
  63. }
  64. }
  65. }
  66. }
  67. }
  68. //MapperAnnotationBuilder类中
  69. public void parse() {
  70. String resource = type.toString();
  71. if (!configuration.isResourceLoaded(resource)) {
  72. //通过xml文件解析
  73. loadXmlResource();
  74. configuration.addLoadedResource(resource);
  75. assistant.setCurrentNamespace(type.getName());
  76. parseCache();
  77. parseCacheRef();
  78. //获得接口的方法(为了获取方法上的注解,通过注解的方式来让方法于sql语句相关联)
  79. Method[] methods = type.getMethods();
  80. for (Method method : methods) {
  81. try {
  82. // issue #237
  83. if (!method.isBridge()) {
  84. //具体的解析过程
  85. parseStatement(method);
  86. }
  87. } catch (IncompleteElementException e) {
  88. configuration.addIncompleteMethod(new MethodResolver(this, method));
  89. }
  90. }
  91. }
  92. parsePendingMethods();
  93. }

具体的调用过程就不细跟了,无非就是获取节点,获取属性值,(或者是获取方法,然后获取注解信息),巴拉巴拉……然后设置到configuration中。
上面要注意的点是,若既配置xml又配置注解的情况下,注解会覆盖xml,原因非常简单,源码中注解解析在xml解析后面,然后覆盖的情况是,他们有相同的namespace+id。
然后我们继续我们的主线任务,就是mapper的设计架构。从上面我们可以知道,configuration中有一个MapperRegistry类型的字段mapperRegistry,其中有一个字段叫knownMappers,knownMappers里面存着key为接口类型,值为MapperProxyFactory的。

  1. //我们在调用TDemoMapper mapper = sqlsession.getMapper(TDemoMapper.class);的时候
  2. //先调用DefaultSqlSession类下的
  3. @Override
  4. public <T> T getMapper(Class<T> type) {
  5. return configuration.<T>getMapper(type, this);
  6. }
  7. //然后调用Configuration类下的
  8. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  9. return mapperRegistry.getMapper(type, sqlSession);
  10. }
  11. //最后调用MapperRegistry类下的
  12. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  13. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  14. if (mapperProxyFactory == null) {
  15. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  16. }
  17. try {
  18. //实际生成是这段代码,通过mapperProxyFactory来生成实例对象
  19. return mapperProxyFactory.newInstance(sqlSession);
  20. } catch (Exception e) {
  21. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  22. }
  23. }
  24. //实际调用类
  25. public class MapperProxyFactory<T> {
  26. private final Class<T> mapperInterface;
  27. private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  28. public T newInstance(SqlSession sqlSession) {
  29. //实例化一个代理类
  30. final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  31. //通过这个函数实例化
  32. return newInstance(mapperProxy);
  33. }
  34. protected T newInstance(MapperProxy<T> mapperProxy) {
  35. //动态代理的基本操作(说明最终实现方式是动态代理)
  36. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  37. }
  38. }
  39. public class MapperProxy<T> implements InvocationHandler, Serializable {
  40. private static final long serialVersionUID = -6424540398559729838L;
  41. private final SqlSession sqlSession;
  42. private final Class<T> mapperInterface;
  43. private final Map<Method, MapperMethod> methodCache;
  44. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  45. this.sqlSession = sqlSession;
  46. this.mapperInterface = mapperInterface;
  47. this.methodCache = methodCache;
  48. }
  49. //动态代理中最重要的方法invoke
  50. @Override
  51. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  52. try {
  53. if (Object.class.equals(method.getDeclaringClass())) {
  54. //如果是Object中的方法就不走下面的代理了,直接执行(比如toString,hashCode)
  55. return method.invoke(this, args);
  56. } else if (isDefaultMethod(method)) {
  57. //如果不是静态方法而且不是抽象方法,则不增强方法
  58. return invokeDefaultMethod(proxy, method, args);
  59. }
  60. } catch (Throwable t) {
  61. throw ExceptionUtil.unwrapThrowable(t);
  62. }
  63. //实际我们的mapper接口的方法走的逻辑就是下面这2条
  64. final MapperMethod mapperMethod = cachedMapperMethod(method);
  65. return mapperMethod.execute(sqlSession, args);
  66. }
  67. }

总结

我们通过sqlSession获得mapper方法,而sqlSession从configuration中的mapperRegistry中获取MapperProxyFactory对象,在通过MapperProxyFactory对象的newInstance方法得到MapperProxy的动态代理实例对象。

我们使用的mapper其实是通过MapperProxy动态代理,在运行时候生成的一个新的对象进行方法增强的,里面的接口方法都会通过下面2个语句进行数据库的操作,以及后续对数据的处理

  1. final MapperMethod mapperMethod = cachedMapperMethod(method);
  2. return mapperMethod.execute(sqlSession, args);12

这两条语句其实包含对访问数据库对象的创建,访问数据库到得到数据库返回数据后的处理等内容,非常复杂,本篇就到此为止。

相关文章