• mybatis原理及整合spring原理


    话题导入:首先我一开始接触mybatis的时候,还是在做SSM课程设计,我会在项目的spring配置文件中会有如下配置:

    
        
        
            
            
            
            
            
            
            
        
        
        
            
        
        
        
            
            
            
            
            
            
        
        
        
            
            
        
    复制代码

    \

    准备知识:

    1、jdbc使用

    public class DbUtil {
    
        public static final String URL = "jdbc:mysql://localhost:3306/imooc";
        public static final String USER = "liulx";
        public static final String PASSWORD = "123456";
    
        public static void main(String[] args) throws Exception {
            //1.加载驱动程序
            Class.forName("com.mysql.jdbc.Driver");
            //2. 获得数据库连接
            Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
            //3.操作数据库,实现增删改查
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
            //如果有数据,rs.next()返回true
            while(rs.next()){
                System.out.println(rs.getString("user_name")+" 年龄:"+rs.getInt("age"));
            }
            
            //prepareStatement用法
            //sql
            String sql = "INSERT INTO imooc_goddess(user_name, sex, age, birthday, email, mobile,"+
                "create_user, create_date, update_user, update_date, isdel)"
                    +"values("+"?,?,?,?,?,?,?,CURRENT_DATE(),?,CURRENT_DATE(),?)";
            //预编译
            PreparedStatement ptmt = conn.prepareStatement(sql); //预编译SQL,减少sql执行
    
            //传参
            ptmt.setString(1, g.getUser_name());
            ptmt.setInt(2, g.getSex());
            ptmt.setInt(3, g.getAge());
            ptmt.setDate(4, new Date(g.getBirthday().getTime()));
            ptmt.setString(5, g.getEmail());
            ptmt.setString(6, g.getMobile());
            ptmt.setString(7, g.getCreate_user());
            ptmt.setString(8, g.getUpdate_user());
            ptmt.setInt(9, g.getIsDel());
    
            //执行
            ptmt.execute();
        }
    }
    复制代码

    2、动态代理

    说到动态代理前先讲下静态代理:

    \

    \

    \

    先从静态代理说起:

    生活举列子:江俊想买一辆车子,想直接去大众厂家拿车,但是拿不到,就只能从经销商(4儿子)那里拿车,但是4儿子想赚钱,就会强制让我们买装潢(贴膜,导航,行车记录仪),还会让我们买保养(购车后开了1w公里去他们那里保养一下),4儿子就是中间的代理(Proxy)

    //汽车售卖接口
    public interface CarSale {
        /**
         * 卖车
         */
        void saleCar();
    }
    
    //经销商卖车
    public class CarFactorySale implements CarSale {
        /**
         * 卖车
         */
        @Override
        public void saleCar() {
            System.out.println("成本价卖车");
        }
    }
    
    //4儿子店卖车
    public class FourSsale implements CarSale{
        /**
         * 厂家
         */
        private CarFactorySale carFactorySale;
    
        public FourSsale(CarFactorySale carFactorySale) {
            this.carFactorySale = carFactorySale;
        }
        /**
         * 卖车
         */
        @Override
        public void saleCar() {
            System.out.println("必须先来买我的装潢");
            carFactorySale.saleCar();
            System.out.println("买完车,之后必须到我这里做保养");
        }
    }
    
    
    小结:代理类与实际类都要基础同一个接口,代理类中引入了实际类,代理类其实就是对实际类功能的增强。
    复制代码

    题外话:是不是想到了切面?

    接下来是jdk动态代理(必须有接口):以helloWorld为例子

    //接口类
    public interface HelloWorld {
        void sayHello();
    }
    
    //实际类
    public class HelloWorldImpl implements HelloWorld {
        @Override
        public void sayHello() {
            System.out.println("hello world");
        }
    }
    
    //调用处理器
    public class MyInvocationHandler implements InvocationHandler{
        //传入实际类
        private Object realTarget;
    
        public MyInvocationHandler(Object target) {
            this.realTarget = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("method:" + method.getName() + "is invoked!");
            System.out.println("在执行实际类逻辑之前,我在。。。");
            Object returnObject = method.invoke(realTarget, args);
            System.out.println("在执行实际类逻辑之后,我在。。。");
            return returnObject;
        }
    }
    
    //测试类
    public class JDKProxyTest {
        public  void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //创建代理类 代理类class内容见下一段代码
            Class proxyClass = Proxy.getProxyClass(JDKProxyTest.class.getClassLoader(), HelloWorld.class);
            //获取代理类构造方法
            final Constructor constructors = proxyClass.getConstructor(InvocationHandler.class);
            //new一个调度器(里面包含增强逻辑)
            final InvocationHandler invocationHandler = new MyInvocationHandler(new HelloWorldImpl());
            //将包含增强逻辑的调度器作为入池传入代理类构造方法,new一个代理类
            HelloWorld helloWorld = (HelloWorld)constructors.newInstance(invocationHandler);
            //执行代理类的业务方法
            helloWorld.sayHello();
    
            // 保存生成的代理类的字节码文件
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            //写法二
    //        HelloWorld helloWorld = (HelloWorld) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(),
    //                new Class[]{HelloWorld.class}, new MyInvocationHandler(new HelloWorldImpl()));
    //        helloWorld.sayHello();
    
        }
    }
    
    //jdk帮我们生成的代理类
    public final class $Proxy0 extends Proxy implements HelloWorld {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        //包含调度器的构造方法
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        //代理方法
        public final void sayHello() throws  {
            try {
                //最终还是调用调用器中的增强方法
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
        
        
        //以下三个方法为jdk代理生成的Object下的三个方法
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m3 = Class.forName("component.HelloWorld").getMethod("sayHello");
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    
    小结:动态代理的代理类不用像静态代理那样必须手写一个,而是由jdk生成class文件。这就是静态和动态的区别。
    我理解的最大好处就是相比静态代理,动态代理不必为一个新接口就重新写一个调度器,而静态代理如果来了一个新接口 就必须
    再写一个代理类。
    查资料得知:springAop中用的就是动态代理,统一拦截所有接口请求。如果是静态代理就做不到了,
    得一个个写代理类,明显是不可取的。
    复制代码

    3、xml解析

    mybatis中很重要的第一步便是解析配置文件,以及mapper.xml

    解析用的都是W3C包下的Document以及javax.xml包下的xpath

    
      
         city
         beijing
      
      
          district
          chaoyang
      
    
    复制代码
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.xpath.XPath;
    import javax.xml.xpath.XPathConstants;
    import javax.xml.xpath.XPathFactory;
     
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
     
    public class XPathTest {
    	
    	public static void main(String args[]){
    		  try {
    			  //解析文档
    			  DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
    			  domFactory.setNamespaceAware(true); // never forget this!
    			  DocumentBuilder builder = domFactory.newDocumentBuilder();
    			  Document doc = builder.parse("city.xml");
    			  
    			   XPathFactory factory = XPathFactory.newInstance(); //创建 XPathFactory
    			   XPath xpath = factory.newXPath();//用这个工厂创建 XPath 对象
    			  
    			   NodeList nodes = (NodeList)xpath.evaluate("location/property", doc, XPathConstants.NODESET);
    			   String name = "";
    			   String value = "";
    			   for (int i = 0; i < nodes.getLength(); i++) {
    				    Node node = nodes.item(i);  
    			        name = (String) xpath.evaluate("name", node, XPathConstants.STRING);
    			        value = (String) xpath.evaluate("value", node, XPathConstants.STRING);
    			        System.out.println("name="+name+";value="+value);
    			   }
    			  
    			  
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
     
    }
    复制代码

    进入正题:

    步骤1:读取配置文件,生成全局Configuration对象

    一、解析配置

    不整合spring使用mybatis示例代码:

    public static void main(String[] args) {
            String resource = "mybatis-config.xml";
            Reader reader;
            try {
                //将XML配置文件构建为Configuration配置类
                reader = Resources.getResourceAsReader(resource);
                // 通过加载配置文件流构建一个SqlSessionFactory  DefaultSqlSessionFactory
                SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
                // 数据源 执行器  DefaultSqlSession
                SqlSession session = sqlMapper.openSession();
                try {
                    // 执行查询 底层执行jdbc
                    User user = (User)session.selectOne("com.tuling.mapper.UserMapper.selectById", 1);
    
                    /*UserMapper mapper = session.getMapper(UserMapper.class);
                    System.out.println(mapper.getClass());
                    User user = mapper.selectById(1L);*/
                    session.commit();
                    System.out.println(user.getUserName());
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    session.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    复制代码

    \

    //获取sqlsessionFactory
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
    复制代码
    //解析配置入口
    public Configuration parse() {
        //如果已经解析过了,报错
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
    //   
    //   
    //   
    //   
    //   
    //   
    //   
    //   
    //   
    //   
    //   
    //   
    //   
    //  
    //   
    //   
    //   
    //  
        
        //根节点是configuration
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    复制代码

    \

    //解析配置文件中每一种配置
      private void parseConfiguration(XNode root) {
        try {
          //分步骤解析
          //issue #117 read properties first
          //1.properties
          propertiesElement(root.evalNode("properties"));
          //2.类型别名
          typeAliasesElement(root.evalNode("typeAliases"));
          //3.插件
          pluginElement(root.evalNode("plugins"));
          //4.对象工厂
          objectFactoryElement(root.evalNode("objectFactory"));
          //5.对象包装工厂
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          //6.设置
          settingsElement(root.evalNode("settings"));
          // read it after objectFactory and objectWrapperFactory issue #631
          //7.环境
          environmentsElement(root.evalNode("environments"));
          //8.databaseIdProvider
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          //9.类型处理器
          typeHandlerElement(root.evalNode("typeHandlers"));
          //10.映射器
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    复制代码

    接下来详细介绍如何解析mapper文件:

    //解析mapper所在路径
    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              //10.4自动扫描包下所有映射器
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
    复制代码
    //扫描mapper所在包路径,获取所有接口包 遍历添加
    public void addMappers(String packageName, Class superType) {
        //查找包下所有是superType的类
        ResolverUtil> resolverUtil = new ResolverUtil>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set>> mapperSet = resolverUtil.getClasses();
        for (Class mapperClass : mapperSet) {
          addMapper(mapperClass);
        }
      }
    复制代码
    //添加一个映射,及寻找xml文件
      public  void addMapper(Class type) {
        //mapper必须是接口!才会添加
        if (type.isInterface()) {
          if (hasMapper(type)) {
            //如果重复添加了,报错
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
              //***这一步就是新建出代理类工厂放入一个map中,便于之后取出调用
            knownMappers.put(type, new MapperProxyFactory(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            //扫描sql注解
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            //寻找xml文件
            parser.parse();
            loadCompleted = true;
          } finally {
            //如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之?
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    复制代码
    //加载与接口同名的mapper xml文件
    private void loadXmlResource() {
        // Spring may not know the real resource name so we check a flag
        // to prevent loading again a resource twice
        // this flag is set at XMLMapperBuilder#bindMapperForNamespace
        if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
            // 将mapper接口转为xml路径 比如com.burton.usermapper -> com/burton/usermapper.xml
          String xmlResource = type.getName().replace('.', '/') + ".xml";
          InputStream inputStream = null;
          try {
            inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
          } catch (IOException e) {
            // ignore, resource is not required
          }
          if (inputStream != null) {
            XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
            //解析mapper的xml文件
              xmlParser.parse();
          }
        }
      }
    复制代码
    public void parse() {
        //如果没有加载过再加载,防止重复加载
        if (!configuration.isResourceLoaded(resource)) {
          //配置解析mapper
          configurationElement(parser.evalNode("/mapper"));
          //标记一下,已经加载过了
          configuration.addLoadedResource(resource);
          //绑定映射器到namespace
          bindMapperForNamespace();
        }
    
        //还有没解析完的东东这里接着解析?  
        parsePendingResultMaps();
        parsePendingChacheRefs();
        parsePendingStatements();
      }
    复制代码

    \

    //解析mapper文件里面内容
    private void configurationElement(XNode context) {
        try {
          //1.配置namespace
          String namespace = context.getStringAttribute("namespace");
          if (namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          builderAssistant.setCurrentNamespace(namespace);
          //2.配置cache-ref
          cacheRefElement(context.evalNode("cache-ref"));
          //3.配置cache 二级缓存 不是很重要 责任链模式 很多种cache一层套一层
          cacheElement(context.evalNode("cache"));
          //4.配置parameterMap(已经废弃,老式风格的参数映射)
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          //5.配置resultMap(高级功能)
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          //6.配置sql(定义可重用的 SQL 代码段)
          sqlElement(context.evalNodes("/mapper/sql"));
          //7.配置select|insert|update|delete TODO
          **buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
        }
      }
    
     //构建语句
      private void buildStatementFromContext(List list, String requiredDatabaseId) {
        for (XNode context : list) {
          //构建所有语句,一个mapper下可以有很多select
          //语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
              //**核心XMLStatementBuilder.parseStatementNode
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
              //如果出现SQL语句不完整,把它记下来,塞到configuration去
            configuration.addIncompleteStatement(statementParser);
          }
        }
      }
    
    
        /**
        statementParser.parseStatementNode()方法中有langDriver.createSqlSource方法负责将sql语句解析成一层层的sqlNode封装在sqlSource中
         * 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的
         * sql脚本对象  .  解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析
         */
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
    
    //最终将mapper文件中某一句增删改查的全部信息(sql语句解析为sqlsource,还有paramtype,resultMap等)构建出MappedStatement,加入configration的map中,key为namespace+id
      public MappedStatement addMappedStatement(
          String id,
          SqlSource sqlSource,
          StatementType statementType,
          SqlCommandType sqlCommandType,
          Integer fetchSize,
          Integer timeout,
          String parameterMap,
          Class parameterType,
          String resultMap,
          Class resultType,
          ResultSetType resultSetType,
          boolean flushCache,
          boolean useCache,
          boolean resultOrdered,
          KeyGenerator keyGenerator,
          String keyProperty,
          String keyColumn,
          String databaseId,
          LanguageDriver lang,
          String resultSets) {
        
        if (unresolvedCacheRef) {
          throw new IncompleteElementException("Cache-ref not yet resolved");
        }
        
        //为id加上namespace前缀
        id = applyCurrentNamespace(id, false);
        //是否是select语句
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
        //又是建造者模式
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
        statementBuilder.resource(resource);
        statementBuilder.fetchSize(fetchSize);
        statementBuilder.statementType(statementType);
        statementBuilder.keyGenerator(keyGenerator);
        statementBuilder.keyProperty(keyProperty);
        statementBuilder.keyColumn(keyColumn);
        statementBuilder.databaseId(databaseId);
        statementBuilder.lang(lang);
        statementBuilder.resultOrdered(resultOrdered);
        statementBuilder.resulSets(resultSets);
        setStatementTimeout(timeout, statementBuilder);
    
        //1.参数映射
        setStatementParameterMap(parameterMap, parameterType, statementBuilder);
        //2.结果映射
        setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
        setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
    
        MappedStatement statement = statementBuilder.build();
        //建造好调用configuration.addMappedStatement
        configuration.addMappedStatement(statement);
        return statement;
      }
    复制代码

    总结:当mybatis配置文件里配置了dao层接口的包名,mybatis会去对应的报下获取所有的接口,遍历,分别再找到同名的mapper.xml文件。解析xml文件,将resultMap,sql,insert,select,delete,update(增删改查都会封装mapperedStatement存到map中 key为namaspace+id,mapperedStatement中包含sqlSource,sqlSource中是sqlNode)都封装进全局configration对象中。

    所有配置都解析到configration对象中,configration对象封装在sqlSessionFactory中,通过sqlSessionFactory获取sqlSession,所以sqlsession中也有configration。

    **sqlSession—configration-**MapperRegistry-knownMappers(一个map,包含接口class为key, 接口代理工厂MapperProxyFactory

    (通过这个工厂拿到代理类(MapperProxyFactory.newInstance),真正执行拦截器以及sql在代理类MapperProxy的invoke方法里,通过sqlSwssion.getMapper就能通过以上步骤拿到代理类)为value,mapper文件解析过了,则存在这个knownmappers中,不允许重复解析mapper文件,是在解析配置文件中mapper所在路径的时候放到这个map中的,key为mapper的class,value为MapperProxyFactory,这个类可以生成动态代理)

    \

    Configuration类中mappedStatements字段是一个map,保存了解析mapper.xml中的select update insert等,map的value是MappedStatement就是一个select/ update/ insert语句解析好的信息(解析得到sqlNode),key是mapper文件的namespace+select update insert的id

    【其他】1、TypeHandler 类型转换器 负责设置sql语句prepareStatement参数Java类型转为数据库类型 以及查到结果将resultSet中数据库结果类型转为java类型(其实就是使用jdbc)。各种转换器注册在TypeHandlerRegister中。

    2、#{id} 在后续会解析为sql中的?,最后给prepareStatement设置参数参数时,会把?的值设置为id的值(通过typeHandler)。

    \

    二、获取mapper代理

    一、讲mapper(整合spring我们一般都是这么使用)代理之前先讲下,另一种调用方式通过statmentId执行sql(mapper文件中的一句sql会封装未一个mapperedStatement)

    // 执行查询 底层执行jdbc com.tuling.mapper.UserMapper.selectById为statementId
    User user = (User)session.selectOne("com.tuling.mapper.UserMapper.selectById", 1);
    
    /*UserMapper mapper = session.getMapper(UserMapper.class);
                    System.out.println(mapper.getClass());
                    User user = mapper.selectById(1L);*/
    session.commit();
    System.out.println(user.getUserName());
    复制代码

    二、执行sql前获取mapper接口的代理

    //defaultSqlSession的方法:获取mapper  
    public  T getMapper(Class type) {
        //最后会去调用MapperRegistry.getMapper
        return configuration.getMapper(type, this);
      }
    
     public  T getMapper(Class type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
    
      //MapperRegistry类的getMapper方法 返回代理类
      public  T getMapper(Class type, SqlSession sqlSession) {
        final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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类通过jdk动态代理构建代理类
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    //是不是看到了熟悉的动态代理
      protected T newInstance(MapperProxy mapperProxy) {
        //用JDK自带的动态代理生成映射器,参数mapperProxy相当于之前讲的任务调度器,也肯定实现了InvocationHandler jdk调度接口
        //最后的真正sql调用数据库肯定再这个代理类里发起
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
    //MapperProxy是真正执行逻辑的地方,MapperProxy的invock方法里会真正去拼装sql,jdbc执行sql
    复制代码

    总结:获取mapper代理类的流程:

    1. 从sqlSession中获取mapper
    2. 从configration中获取mapper
    3. 从MapperRegistry中获取mapper
    4. 从knownMappers中获取mapper的动态代理类工厂
    5. 用工厂生成mapper的代理类(代理中传入了sqlSession)

    三、Executor执行sql

    3、执行sql

    //mapper调度器(代理类是jdk生成的字节码文件,最后代理类会调这个调度器,逻辑在调度器里)
    public class MapperProxy implements InvocationHandler, Serializable {
    
      private static final long serialVersionUID = -6424540398559729838L;
      private final SqlSession sqlSession;
      private final Class mapperInterface;
      private final Map methodCache;
    
      public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
        //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
        if (Object.class.equals(method.getDeclaringClass())) {
          try {
            return method.invoke(this, args);
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
        //这里优化了,去缓存中找MapperMethod
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //执行
        return mapperMethod.execute(sqlSession, args);
      }
    
      //去缓存中找MapperMethod
      private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
          //找不到才去new
          mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
          methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
      }
    
    }
    复制代码
    //去缓存中找MapperMethod或者新建mapperMehod
      private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
          //找不到才去new
          mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
          methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
      }
    
    //构造器新建mapperMethod
     public MapperMethod(Class mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, method);
      }
    
    //新建MethodSignature 方法签名相关信息
      public MethodSignature(Configuration configuration, Method method) {
          this.returnType = method.getReturnType();
          this.returnsVoid = void.class.equals(this.returnType);
          this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
          this.mapKey = getMapKey(method);
          this.returnsMap = (this.mapKey != null);
          this.hasNamedParameters = hasNamedParams(method);
          //以下重复循环2遍调用getUniqueParamIndex,是不是降低效率了
          //记下RowBounds是第几个参数
          this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
          //记下ResultHandler是第几个参数
          this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
          **this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
        }
    
    //获取接口入参
    //得到所有参数
        private SortedMap getParams(Method method, boolean hasNamedParameters) {
          //用一个TreeMap,这样就保证还是按参数的先后顺序
          final SortedMap params = new TreeMap();
          final Class[] argTypes = method.getParameterTypes();
          for (int i = 0; i < argTypes.length; i++) {
            //是否不是RowBounds/ResultHandler类型的参数
            if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
              //参数名字默认为0,1,2,这就是为什么xml里面可以用#{1}这样的写法来表示参数了
              String paramName = String.valueOf(params.size());
              if (hasNamedParameters) {
                //还可以用注解@Param来重命名参数
                paramName = getParamNameFromAnnotation(method, i, paramName);
              }
              params.put(i, paramName);
            }
          }
          return params;
        }
    
    //解析入参@param参数
     private String getParamNameFromAnnotation(Method method, int i, String paramName) {
          final Object[] paramAnnos = method.getParameterAnnotations()[i];
          for (Object paramAnno : paramAnnos) {
            if (paramAnno instanceof Param) {
              paramName = ((Param) paramAnno).value();
            }
          }
          return paramName;
        }
    
    //将方法入参转化为xml文件中用#{}可以解析出来的参数
    public Object convertArgsToSqlCommandParam(Object[] args) {
          final int paramCount = params.size();
          if (args == null || paramCount == 0) {
            //如果没参数
            return null;
          } else if (!hasNamedParameters && paramCount == 1) {
            //如果只有一个参数
            return args[params.keySet().iterator().next().intValue()];
          } else {
            //否则,返回一个ParamMap,修改参数名,参数名就是其位置
            final Map param = new ParamMap();
            int i = 0;
            for (Map.Entry entry : params.entrySet()) {
              //1.先加一个#{0},#{1},#{2}...参数
              param.put(entry.getValue(), args[entry.getKey().intValue()]);
              // issue #71, add param names as param1, param2...but ensure backward compatibility
              final String genericParamName = "param" + String.valueOf(i + 1);
              if (!param.containsKey(genericParamName)) {
                //2.再加一个#{param1},#{param2}...参数
                //你可以传递多个参数给一个映射器方法。如果你这样做了, 
                //默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
                //如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。 
                param.put(genericParamName, args[entry.getKey()]);
              }
              i++;
            }
            return param;
          }
        }
    //通过上述代码,我们在xml文件中取出入参可以用#{0},#{param0},#{paramName}三种方式
    复制代码 
    
    //执行数据库操作
      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
        if (SqlCommandType.INSERT == command.getType()) {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.insert(command.getName(), param));
        } else if (SqlCommandType.UPDATE == command.getType()) {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.update(command.getName(), param));
        } else if (SqlCommandType.DELETE == command.getType()) {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.delete(command.getName(), param));
        } else if (SqlCommandType.SELECT == command.getType()) {
          if (method.returnsVoid() && method.hasResultHandler()) {
            //如果有结果处理器
            executeWithResultHandler(sqlSession, args);
            result = null;
          } else if (method.returnsMany()) {
            //如果结果有多条记录
            result = executeForMany(sqlSession, args);
          } else if (method.returnsMap()) {
            //如果结果是map
            result = executeForMap(sqlSession, args);
          } else {
            //否则就是一条记录
            Object param = method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(command.getName(), param);
          }
        } else {
          throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
          throw new BindingException("Mapper method '" + command.getName() 
              + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
      }
    复制代码

    我们就选一个最简单的select返回一条跟进源码看一下

    //核心selectOne
      @Override
      public  T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        //转而去调用selectList,很简单的,如果得到0条则返回null,得到1条则返回1条,得到多条报TooManyResultsException错
        // 特别需要主要的是当没有查询到结果的时候就会返回null。因此一般建议在mapper中编写resultType的时候使用包装类型
        //而不是基本类型,比如推荐使用Integer而不是int。这样就可以避免NPE
        List list = this.selectList(statement, parameter);
        if (list.size() == 1) {
          return list.get(0);
        } else if (list.size() > 1) {
          throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
          return null;
        }
      }
    
    看到TooManyResultsException这个异常是不是有点熟悉,可能我们代码会出现这个异常。
    返回类型最好用包装类,避免查不到结果返回null,导致控制住
    复制代码
    //从configration中根据namespace+方法id去粗mapperedStatment
      public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          //根据statement id找到对应的MappedStatement
          MappedStatement ms = configuration.getMappedStatement(statement);
          //转而用执行器来查询结果,注意这里传入的ResultHandler是null
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    
    获取解析过的sql对象
     //SqlSession.selectList会调用此方法
      @Override
      public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //得到绑定sql,该boundsql中的sql为解析后的sql,动态sql中若参数是${},则会直接解析成参数,若参数是#{},则会解析成?,解析成?的会在下文stmt = prepareStatement方法中替换为参数
        BoundSql boundSql = ms.getBoundSql(parameter);
        //创建缓存Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //查询
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
     
    //创建statmentHandler以及prepareStatment(jdbc)
      public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          //新建一个StatementHandler
          //这里看到ResultHandler传入了
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          //准备语句
          stmt = prepareStatement(handler, ms.getStatementLog());
          //StatementHandler.query
          return handler.query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    
    //给动态sql中#{}的在boundSql的?替换为真实参数
      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
            Connection connection = this.getConnection(statementLog);
            Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
            handler.parameterize(stmt);
            return stmt;
        }
    
    //jdbc执行
      public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
        String sql = boundSql.getSql();
        statement.execute(sql);
        //先执行Statement.execute,然后交给ResultSetHandler.handleResultSets
        return resultSetHandler.handleResultSets(statement);
      }
    复制代码

    小结:调接口查询时,首先执行了调度器中方法,创建mapperedMethod,并且创建方法签名,比如会把方法入参获取放入一个map中,key时0,1,2,3.。。。param0,param1.。。paramName。根据方法的包名及方法名从configration中获取先前解析好的mapperdStatement,里面包含解析过的sql语句。调jdbc进行执行查数据库。

    \

    mybatis中重要的几个类:

    1. Configration 全局配置类
    2. MapperedStatement 封装一个sql(select/insert/update/delete)信息。MapperMethod封装了一个sql,也是根据当前该sql的类型(select/insert/update/delete)发起sqlSession执行总入口
    3. BoundSql 直接存储sql的类,将#{}转换为?后的sql
    4. Executor sql执行类 所有sql语句执行都下这个类方法里
    5. MapperRegistry,包含一个Map knownMappers的map属性,key为一个mapper接口类的class,value为这个mapper代理类的工程

    \

    四、整合spring的原理

    当我们使用@Autowired注解的时候有没有想过,为啥能这么简单得 我们什么都没做就能拿到这了一个类,然后还能执行我们写在mapper.xml中的sql?神奇啊

    1、首先看过获取mapper代理流程的应该清楚 为啥能从一个mapper的java接口就能执行我们写在mapper.xml中的sql? 动态代理!!

    2、为啥这个动态代理类能被我们用@autowired注入进来?肯定是这个 代理类在spring容器中!!

    那么最终的问题就变成这个代理类是怎么到spring容器中的?下面进行一步步解析:

    mybatis整合spring的一个javaconfig配置类:

    主要有两个重点:1、@MapperScan 2、SqlSessionFactoryBean.getObject

    @Configuration
    // 重点看这里 重点看这里 重点看这里 重点看这里 MapperScan MapperScan 这里就是spring整合的重点
    @MapperScan(basePackages = "com.wwdz.mall.mapper.single", sqlSessionTemplateRef = "singleSqlTemplate")
    public class DataSourceSingleConfig {
    
        @Bean(name = "rdsPolar")
        @ConfigurationProperties(prefix = "spring.datasource.rds-polar")
        public DataSource dataSourceRdsPolar() {
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setName("rdsPolar");
            DatasourceUtil.setDruidProperty(dataSource);
            return dataSource;
        }
    
        /** 重点看这里 重点看这里 重点看这里
        构建SqlSessionFactory的过程,和单独使用mybatis一样,就是解析mybatis配置文件,把所有配置配置放到configuration中,然后configuration
        最终放到sqlSession中(当然包括解析mapper.xml文件)
        **/
        @Bean("singleSqlFactory")
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSourceRdsPolar());
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/single/*.xml"));
            // 这句代码就是开始解析 下面会介绍
            return sqlSessionFactoryBean.getObject();
        }
    
        @Bean("singleSqlTemplate")
        public SqlSessionTemplate sqlSessionTemplate(@Qualifier("singleSqlFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    
        @Bean(name = "singleTransactionManager")
        public PlatformTransactionManager singleTransactionManager(@Qualifier("rdsPolar") DataSource dataSource) throws Exception {
            return new DataSourceTransactionManager(dataSource);
        }
    
        @Bean(name = "singleTransactionTemplate")
        public TransactionTemplate transactionTemplate(@Qualifier("singleTransactionManager") PlatformTransactionManager mybatisTransactionManager) {
            TransactionTemplate transactionTemplate = new TransactionTemplate();
            transactionTemplate.setIsolationLevel(ISOLATION_DEFAULT);
            transactionTemplate.setTimeout(3);
            transactionTemplate.setTransactionManager(mybatisTransactionManager);
            return transactionTemplate;
        }
    }
    复制代码

    1、SqlSessionFactoryBean.getObject->SqlSessionFactoryBean.afterPropertiesSet->SqlSessionFactoryBean.buildSqlSessionFactory 这一串流程会解析SqlSessionFactoryBean中配置的配置信息最终解析出来都放到configuration中,然后把configuration放到sqlSessionFactory中,返回sqlSessionFactory;

    2、重点关注@MapperScan

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Import({MapperScannerRegistrar.class}) // 重点看这里啊啊 MapperScannerRegistrar这个类
    @Repeatable(MapperScans.class)
    public @interface MapperScan {
        String[] value() default {};
    
        String[] basePackages() default {};
    
        Class[] basePackageClasses() default {};
    
        Class nameGenerator() default BeanNameGenerator.class;
    
        Class annotationClass() default Annotation.class;
    
        Class markerInterface() default Class.class;
    
        String sqlSessionTemplateRef() default "";
    
        String sqlSessionFactoryRef() default "";
    
        Class factoryBean() default MapperFactoryBean.class;
    }
    
    
    /**
    这个类继承了spring的ImportBeanDefinitionRegistrar,需要实现registerBeanDefinitions方法
    **/
    public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
        private ResourceLoader resourceLoader;
    
        public MapperScannerRegistrar() {
        }
    
        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }
    
        // 这个方法是继承自spring中的ImportBeanDefinitionRegistrar
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            // 获取@MapperScan注解上的属性
            AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
            if (mapperScanAttrs != null) {
                this.registerBeanDefinitions(mapperScanAttrs, registry);
            }
    
        }
    
        void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
            // 重点 看这里
            // 这个ClassPathMapperScanner类也很关键 是用来扫码指定路径的mapper接口变为BeanDefinition的
            // 这个类继承自spring的ClassPathBeanDefinitionScanner,spring的这个类的作用是在spring容器启动的过程中,
            // 去读取指定路径下的类变成BeanDefinition,但是只会读普通类,接口和抽象类不会读,所以mybatis-spring的需要继承这个类之后对
            // 这个类中的isCandidateComponent方法重写,变为只读取接口,因为我们的mapper.java都是接口。其他方法还是用父类的
            ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
            Optional var10000 = Optional.ofNullable(this.resourceLoader);
            Objects.requireNonNull(scanner);
            var10000.ifPresent(scanner::setResourceLoader);
            Class annotationClass = annoAttrs.getClass("annotationClass");
            if (!Annotation.class.equals(annotationClass)) {
                scanner.setAnnotationClass(annotationClass);
            }
    
            Class markerInterface = annoAttrs.getClass("markerInterface");
            if (!Class.class.equals(markerInterface)) {
                scanner.setMarkerInterface(markerInterface);
            }
    
            Class generatorClass = annoAttrs.getClass("nameGenerator");
            if (!BeanNameGenerator.class.equals(generatorClass)) {
                scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
            }
    
            Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
            if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
                scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
            }
    
            scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
            scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
            List basePackages = new ArrayList();
            basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
            basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
            basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
            scanner.registerFilters();
            // 开始扫描指定包路径下的mapper接口为beanDifinition
            scanner.doScan(StringUtils.toStringArray(basePackages));
        }
    
    }
    复制代码
    ClassPathMapperScanner中重要的几个方法:非常重要
    /**
       * 方法实现说明:真正调用扫描我们@MapperScan指定的路径下的mapper包
       *
       * @author:xsls
       * @param basePackages
       *          :路径长度
       * @return:
       * @exception:
       * @date:2019/8/21 17:27
       * 这个doscan方法其实是重写了spring中父类ClassPathBeanDefinitionScanner的doscan
       */
      @Override
      public Set doScan(String... basePackages) {
        /**
         * 调用父类ClassPathBeanDefinitionScanner 来进行扫描
         * 然后这个方法中会调用isCandidateComponent这个方法,这个方法又是被当前ClassPathMapperScanner这个类重写过了
         * 只会扫描mapper接口,所以最终的结果是会拿到所有mapper接口的beanDefinition 
         * 父类spring中的ClassPathBeanDefinitionScanner的isCandidateComponent只会扫描普通的类成为beanDifinition到ioc容器中,抽象类和接口都不会
         */
        Set beanDefinitions = super.doScan(basePackages);
    
        /**
         * 若扫描后 我们mapper包下有接口类,那么扫描bean定义就不会为空
         */
        if (beanDefinitions.isEmpty()) {
          LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
              + "' package. Please check your configuration.");
        } else {
          /**
           * 正是在这里mybaits做了一个很牛逼的功能,将spring的 的bean定义玩到极致(做了偷天换日的操作) 现在我们知道t通过父类扫描出来的mapper是接口类型的
           * 比如我们com.tuling.mapper.UserMapper 他是一个接口 我们有基础的同学可能会知道我们的bean定义最终会被实例化成
           * 对象,但是我们接口是不能实例化的,所以在processBeanDefinitions 来进行偷天换日
           */
          processBeanDefinitions(beanDefinitions);
        }
    
        return beanDefinitions;
      }
    
      private void processBeanDefinitions(Set beanDefinitions) {
        GenericBeanDefinition definition;
        /**
         * 循环我们所有扫描出mapper的bean定义出来
         */
        for (BeanDefinitionHolder holder : beanDefinitions) {
          // 获取我们的bean定义
          definition = (GenericBeanDefinition) holder.getBeanDefinition();
          // 获取我们的bean定义的名称 这里是mapper接口的名字
          String beanClassName = definition.getBeanClassName();
          LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
              + "' mapperInterface");
    
          /**
           * 进行真的偷天换日操作,也就是这二行代码是最最最最最重要的, 关乎我们 spring整合mybaits的整合 definition.setBeanClass(this.mapperFactoryBeanClass);
           */
          // 设置ConstructorArgumentValues 会通过构造器初始化对象 MapperFactoryBean有一个构造方法 入参为真正mapper接口的beanClassName
          definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
          // 设置成factoryBean this.mapperFactoryBeanClass = MapperFactoryBean.class 把mapper接口的beanDifiniton的class类型设置为MapperFactoryBean,这就是偷天换日
          definition.setBeanClass(this.mapperFactoryBeanClass);
          // 总结下上面两步做的:拿到每一个mapper接口的beanDifiniton,将beanDifiniton中的beanClass设置为MapperFactoryBean.class,然后通过MapperFactoryBean的构造方法设置真正mapper接口的beanclassname
        // MapperFactoryBean是FactoryBean的子类,在spring中,通过beanDifinition创建bean实例的时候会判断这个beanDifinition的beanClass是否是FactoryBean,是的话,则放到spring容器中的对象是FactoryBean的getObject方法返回的对象
            // MapperFactoryBean对象的getObject方法返回的就是mapper接口的jdk代理对象
    
          definition.getPropertyValues().add("addToConfig", this.addToConfig);
    
          /**
           * 为我们的Mapper对象绑定我们的sqlSessionFactory引用 说白了就是我们的UserMapper(实际上是就是为我们的MapperFactoryBean添加一个sqlSessionFactory的属性)
           * 然后SpringIoc在实例话我们的MapperFactoryBean的时候会经历populate()方法为我么你的UserMapper(MapperFactoryBean)
           * 的sqlSessionFactory赋值(调用set方法)
           */
          boolean explicitFactoryUsed = false;
          if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
            definition.getPropertyValues().add("sqlSessionFactory",
                new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionFactory != null) {
            definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
            explicitFactoryUsed = true;
          }
          /**
           * 为我们的Mapper对象绑定我们的sqlSessionTemplate属性对象
           * 说白了就是我们的UserMapper(实际上是就是为我们的MapperFactoryBean添加一个sqlSessionTemplate的属性)
           * 然后SpringIoc在实例话我们的MapperFactoryBean的时候会经历populate()方法为我么你的UserMapper(MapperFactoryBean)
           * 的sqlSessionTemplate赋值(调用set方法)
           */
          if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
            if (explicitFactoryUsed) {
              LOGGER.warn(
                  () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            definition.getPropertyValues().add("sqlSessionTemplate",
                new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionTemplate != null) {
            if (explicitFactoryUsed) {
              LOGGER.warn(
                  () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            // 将sqlSessionTemplate通过AUTOWIRE_BY_TYPE自动装配
            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
          }
    
          /**
           * 设置UserMapper定义的注入模型是通过 包扫描进来的,所有我们的默认注入模型就是
           * AutowireCapableBeanFactory.AUTOWIRE_NO=0注入模型为0的时候,在这种情况下,若我们的MapperFactoryBean
           * 的字段属性是永远自动注入不了值的因为字段上是没有 @AutoWired注解,所以我们需要把UserMapper 的bean定义的注入模型给改成我们的 AUTOWIRE_BY_TYPE
           * = 1,指定这个类型就是根据类型装配的话, 第一:我们的字段上不需要写@AutoWired注解,为啥? springioc会把当前UserMapper中的setXXX(入参)
           * 都会去解析一次入参,入参的值可定会从ioc容器中获取,然后调用setXXX方法给赋值好. 或 AUTOWIRE_By_Type=1
           * 指定这个类型就是根据类型装配的话,第一:我们的字段上不需要写@AutoWired注解,为啥? springioc会把当前UserMapper中的setXXX(入参)都会去解析一次入参,
           * 入参的值可定会从ioc容器中获取,然后调用setXXX方法给赋值好.
           *
           * ??????????????为啥在这里mybaits却要指定AUTOWIRE_BY_TYPE了? 假设我们指定的是by_name的话, 那么他会通过setXXX(入参)的引用名去ioc容器中获取值,
           * 假设我们自己配置的bean的名称不是相同的那么就会抛出异常
           *
           * public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (this.sqlSessionTemplate == null ||
           * sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) { this.sqlSessionTemplate
           * =createSqlSessionTemplate(sqlSessionFactory); } } 所以在IOC实例化我们的UserMapper的时候,会调用父类
           * SqlSessionDaoSupport的setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)
           * 方法,而我们的sqlSessionFactory需要去容器中获取(也就是我们自己配置的SqlSessionFactoryBean)
           *
           */
          if (!explicitFactoryUsed) {
            LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
          }
          /**
           * 设置bean定义的加载模型(是否为懒加载)
           */
          definition.setLazyInit(lazyInitialization);
        }
      }
    
      /**
       * {@inheritDoc}
       * 这里很关键 重写了父类的方法 spring 父类中的这个方法 只会扫描类 不会要接口和抽象类 这里重写为只要接口(mapper接口)
       */
      @Override
      protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
      }
    复制代码
    // spring中的FactoryBean类,作用就是spring容器启动的时候,会把这个类getObject方法返回的对象注册到ioc容器中
    public interface FactoryBean {
        @Nullable
        T getObject() throws Exception;
    
        @Nullable
        Class getObjectType();
    
        default boolean isSingleton() {
            return true;
        }
    }
    复制代码
    /**
     * @vlog: 高于生活,源于生活
     * @desc: 类的描述:这个了就是我们UserMapper的代理类,他也会经过 springIoc容器bean的生命周期,在bean的生命周期方法populate()方法会给属性进行赋值
     *        由于在ClassMapperScan类中已经把当前的bean定义的注入模型给修改了by_type 所以,凡是写了setXXX的方法的,spring ioc在populate() 去进行调用
     * @author: xsls
     * @createDate: 2019/8/22 19:20
     * @version: 1.0
     * MapperFactoryBean 继承了spring的FactoryBean 重点重点重点
     */
    public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
    
      private Class mapperInterface;
    
      private boolean addToConfig = true;
    
      public MapperFactoryBean() {
        // intentionally empty
      }
    
      /**
       * 在这里又是一个牛逼的设计闪光点:我们知道在ClassPathMapperScanner 扫描我们的UserMapper的时候做了一个牛逼的事情,
       * 他扫描我们的UserMapper的时候的bean定义是接口类型的,我们知道接口类型是不能够被实例化的 所以在ClassPathMapperScanner扫描之后马上进行来处理UserMapper的bean定义
       * definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
       * definition.setBeanClass(this.mapperFactoryBeanClass); 把UserMapper的bean定义给改成我们的MapperFactoryBean,
       * 最终我们实例化UserMapper就是我们的MapperFactoryBean类型,
       * definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
       * 就是来指定我们的实例化MapperFactoryBean的构造函数的参数。这么做的目的就是 因为MapperFactoryBean 是我们的Factorybean对象, 最终返回的是getObject()方法放回的对象
       * 而getObject()对象返回的是一个jdk代理对象,我们知道jdk代理对象需要代理接口, 所以这里就是为了保存我们传入进来的接口类型
       *
       * @param mapperInterface
       */
      public MapperFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      /**
       * 方法实现说明:在UserMapper 父类DaoSupport 的bean的生命周期回调
       * InitializingBean.afterPropertiesSet()方法的时候,会进行checkDaoConfig();检查
       *
       * @author:xsls
       * @return:
       * @exception:
       * @date:2019/8/22 20:07
       */
      @Override
      protected void checkDaoConfig() {
        /**
         * 调用父类的SqlSessionDaoSupport的方法来检查我们的SqlSessionFactory 或者sqlSessionTemplate是否为空
         */
        super.checkDaoConfig();
    
        /**
         * 断言我们的mapperInterface(我们mapper接口class类型是否为空)
         */
        notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    
        /**
         * 在这里进行了二个操作,第一步:getSqlSession() 是调用父类的获取SqlSession类型(接口)实现类 SqlSessionTemplate 第二步:getConfiuration
         * 是调用sqlSessionTemplate的sqlSessionFactory对象获取他的Configuration属性
         */
        Configuration configuration = getSqlSession().getConfiguration();
        /**
         * 判断我们的mapperRegistry 的knownMappers Map, MapperProxyFactory> knownMappers = new HashMap<>();
         */
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
          try {
            /**
             * 把我们的接口类型保存到sqlSessionFactory的属性Configuration对象 的MapperRegistry属性中
             */
            configuration.addMapper(this.mapperInterface);
          } catch (Exception e) {
            logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
            throw new IllegalArgumentException(e);
          } finally {
            ErrorContext.instance().reset();
          }
        }
      }
    
      /**
       * 请看这里 最重要的就是这里这一步骤 真实注入到ioc容器的是getSqlSession().getMapper(this.mapperInterface)返回的代理类(这是mybatis原生的代码逻辑了)
       * 方法实现说明:由于是我们factoryBean,那么我们service中注入我们的UserMapper的时候 就会调用我们的getObject()
       *
       * @author:xsls
       * @return:
       * @exception:
       * @date:2019/8/22 20:35
       */
      @Override
      public T getObject() throws Exception {
        /**
         * 第一步:就是获取我么女的SqlSessionTemplate 第二步:获取我们的SqlSessionTemplate.getMapper(mapperInterface)方法
         */
        return getSqlSession().getMapper(this.mapperInterface);
      }
    
      /**
       * {@inheritDoc}
       */
      @Override
      public Class getObjectType() {
        return this.mapperInterface;
      }
    
      /**
       * {@inheritDoc}
       */
      @Override
      public boolean isSingleton() {
        return true;
      }
    
      // ------------- mutators --------------
    
      /**
       * Sets the mapper interface of the MyBatis mapper
       *
       * @param mapperInterface
       *          class of the interface
       */
      public void setMapperInterface(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      /**
       * Return the mapper interface of the MyBatis mapper
       *
       * @return class of the interface
       */
      public Class getMapperInterface() {
        return mapperInterface;
      }
    
      /**
       * If addToConfig is false the mapper will not be added to MyBatis. This means it must have been included in
       * mybatis-config.xml.
       * 

    * If it is true, the mapper will be added to MyBatis in the case it is not already registered. *

    * By default addToConfig is true. * * @param addToConfig * a flag that whether add mapper to MyBatis or not */ public void setAddToConfig(boolean addToConfig) { this.addToConfig = addToConfig; } /** * Return the flag for addition into MyBatis config. * * @return true if the mapper will be added to MyBatis in the case it is not already registered. */ public boolean isAddToConfig() { return addToConfig; } } 复制代码

    画个spring整合mybatis的流程图:

    整合spring总结:

    1、@mapperScan注解引入了MapperScannerRegistrar,这是ImportBeanDefinitionRegistrar的子类,在spring启动中会执行ImportBeanDefinitionRegistrar的registerBeanDefinitions方法往容器中注册beanDefinition。

    2、ClassPathMapperScanner类继承自spring的ClassPathBeanDefinitionScanner,通过该类的doscan方法把mapper接口都扫描并得到对应的beanDefiniton。

    3、将beanDifiniton中的beanClass设置为MapperFactoryBean.class(spring中FactoryBean的子类),然后通过MapperFactoryBean的构造方法设置真正mapper接口的beanclassname,MapperFactoryBean的getObject方法放回的是sqlSesiion.getMapper返回真正的mapper接口的代理对象。

    五、重要类

    • MapperRegistry:有一个属性knownMappers本质上是一个Map,其中的key是Mapper接口的全限定名,value的MapperProxyFactory;
    • MapperProxyFactory:这个类是MapperRegistry中存的value值,在通过 sqlSession获取Mapper时,其实先获取到的是这个工厂,然后通过这个工厂创建 Mapper的动态代理类;
    • MapperProxy:实现了InvocationHandler接口,Mapper的动态代理接口方法的调 用都会到达这个类的invoke方法;
    • MapperMethod:判断你当前执行的方式是增删改查哪一种,并通过SqlSession执 行相应的操作;
    • SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能,Executor会创建好放在这个类里;
    • Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护,一级缓存在这个类里,所以看出一级缓存是sqlSession级别的,每个sqlSesiion都有对应的一级缓存;
    • StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合;
    • ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;
    • ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
    • TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换;
    • MappedStatement:MappedStatement维护了一条节点的封装;
    • SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回;
    • BoundSql:表示动态生成的SQL语句以及相应的参数信息;
    • Configuration:MyBatis所有的配置信息都维持在Configuration对象之中。

    调试主要关注点

    MapperProxy.invoke方法:MyBatis的所有Mapper对象都是通过动态代理生成

    的,任何方法的调用都会调到invoke方法,这个方法的主要功能就是创建

    MapperMethod对象,并放进缓存。所以调试时我们可以在这个位置打个断点,看下是

    否成功拿到了MapperMethod对象,并执行了execute方法。

    MapperMethod.execute方法:这个方法会判断你当前执行的方式是增删改查哪一

    种,并通过SqlSession执行相应的操作。Debug时也建议在此打个断点看下。

    DefaultSqlSession.selectList方法:这个方法获取了获取了MappedStatement对

    象,并最终调用了Executor的query方法;

    总流程图

  • 相关阅读:
    【Redis】共同关注列表与基于Feed流的关注消息滚动分页推送的实现
    如何通过EXPLAIN命令分析PostgreSQL查询计划,以便进行性能优化?
    深度学习 RNN架构解析
    idea中th:onclick报错问题最终解决办法
    大白话讲解IOC和AOP
    Docker实战-使用NGINX实现4层的负载均衡
    kline graph
    【Qt 按钮】QPushButton所有函数和样式
    英语体系----词根词缀等----持续补充(词根词缀等,词汇,语法,简单句,长难句,写作)
    C++——new和delete关键字
  • 原文地址:https://blog.csdn.net/AS011x/article/details/126685733