龙空技术网

就这一篇!mybatis原理及整合spring原理

Java机械师 1199

前言:

今天我们对“mybatis和spring整合原理”大体比较珍视,看官们都想要剖析一些“mybatis和spring整合原理”的相关知识。那么小编同时在网摘上收集了一些有关“mybatis和spring整合原理””的相关文章,希望大家能喜欢,同学们一起来了解一下吧!

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

<!-- ===================整合Mybatis=================== -->    <!-- 配置数据库环境 -->    <bean id="dataSource"        class="org.springframework.jdbc.datasource.DriverManagerDataSource">        <!-- mysql url -->        <property name="url"            value="jdbc:mysql://localhost:3306/mybaits"></property>        <!-- 驱动 -->        <property name="driverClassName"            value="com.mysql.jdbc.Driver"></property>        <!-- 用户名密码 -->        <property name="username" value="root"></property>        <property name="password" value=""></property>    </bean>    <!-- 创建数据库映射器,扫描包下所有接口,并为其创建动态代理类 -->    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">        <property name="basePackage" value="com.project.dao" />    </bean>    <!-- 配置 Mybatis 的 SqlSessionFactory -->    <bean id="sqlSessionFactory"        class="org.mybatis.spring.SqlSessionFactoryBean">        <!-- 指定数据库环境 -->        <property name="dataSource" ref="dataSource"></property>        <!-- 指定Mybatis配置文件 -->        <property name="configLocation"            value="classpath:config/mybatisConfig.xml"></property>        <!-- 指定Mybatis映射文件,*.xml 会匹配所有xml文件 -->        <!-- <property name="mapperLocations" value="classpath:com/project/mapper/*.xml"></property> -->    </bean>    <!-- 创建事务管理器:针对jdbc或Mybatis的事务管理器 -->    <bean id="transactionManager"        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <!-- 指定环境, name的dataSource是固定写法,ref是前面创建的环境id -->        <property name="dataSource" ref="dataSource"></property>    </bean>复制代码

\

准备知识: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

<location>  <property>     <name>city</name>     <value>beijing</value>  </property>  <property>      <name>district</name>      <value>chaoyang</value>  </property></location>复制代码
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();        }    }复制代码

\

//获取sqlsessionFactorypublic 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;//  <?xml version="1.0" encoding="UTF-8" ?> //  <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" //  ";> //  <configuration> //  <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="org/mybatis/example/BlogMapper.xml"/> //  </mappers> //  </configuration>        //根节点是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<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();    for (Class<?> mapperClass : mapperSet) {      addMapper(mapperClass);    }  }复制代码
//添加一个映射,及寻找xml文件  public <T> void addMapper(Class<T> 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<T>(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<XNode> 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为statementIdUser 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> T getMapper(Class<T> type) {    //最后会去调用MapperRegistry.getMapper    return configuration.<T>getMapper(type, this);  } 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类通过jdk动态代理构建代理类  public T newInstance(SqlSession sqlSession) {    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);    return newInstance(mapperProxy);  }//是不是看到了熟悉的动态代理  protected T newInstance(MapperProxy<T> mapperProxy) {    //用JDK自带的动态代理生成映射器,参数mapperProxy相当于之前讲的任务调度器,也肯定实现了InvocationHandler jdk调度接口    //最后的真正sql调用数据库肯定再这个代理类里发起    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);  }//MapperProxy是真正执行逻辑的地方,MapperProxy的invock方法里会真正去拼装sql,jdbc执行sql复制代码

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

从sqlSession中获取mapper从configration中获取mapper从MapperRegistry中获取mapper从knownMappers中获取mapper的动态代理类工厂用工厂生成mapper的代理类(代理中传入了sqlSession)三、Executor执行sql

3、执行sql

//mapper调度器(代理类是jdk生成的字节码文件,最后代理类会调这个调度器,逻辑在调度器里)public class MapperProxy<T> implements InvocationHandler, Serializable {  private static final long serialVersionUID = -6424540398559729838L;  private final SqlSession sqlSession;  private final Class<T> mapperInterface;  private final Map<Method, MapperMethod> methodCache;  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> 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<Integer, String> getParams(Method method, boolean hasNamedParameters) {      //用一个TreeMap,这样就保证还是按参数的先后顺序      final SortedMap<Integer, String> params = new TreeMap<Integer, String>();      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<String, Object> param = new ParamMap<Object>();        int i = 0;        for (Map.Entry<Integer, String> 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> 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<T> list = this.<T>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 <E> List<E> 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 <E> List<E> 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 <E> List<E> 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.<E>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 <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {    String sql = boundSql.getSql();    statement.execute(sql);    //先执行Statement.execute,然后交给ResultSetHandler.handleResultSets    return resultSetHandler.<E>handleResultSets(statement);  }复制代码

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

\

mybatis中重要的几个类:

Configration 全局配置类MapperedStatement 封装一个sql(select/insert/update/delete)信息。MapperMethod封装了一个sql,也是根据当前该sql的类型(select/insert/update/delete)发起sqlSession执行总入口BoundSql 直接存储sql的类,将#{}转换为?后的sqlExecutor sql执行类 所有sql语句执行都下这个类方法里MapperRegistry,包含一个Map<Class, MapperProxyFactory> 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<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;    Class<? extends Annotation> annotationClass() default Annotation.class;    Class<?> markerInterface() default Class.class;    String sqlSessionTemplateRef() default "";    String sqlSessionFactoryRef() default "";    Class<? extends MapperFactoryBean> 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<? extends Annotation> 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<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");        if (!BeanNameGenerator.class.equals(generatorClass)) {            scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));        }        Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {            scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);        }        scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));        scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));        List<String> 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<BeanDefinitionHolder> doScan(String... basePackages) {    /**     * 调用父类ClassPathBeanDefinitionScanner 来进行扫描     * 然后这个方法中会调用isCandidateComponent这个方法,这个方法又是被当前ClassPathMapperScanner这个类重写过了     * 只会扫描mapper接口,所以最终的结果是会拿到所有mapper接口的beanDefinition      * 父类spring中的ClassPathBeanDefinitionScanner的isCandidateComponent只会扫描普通的类成为beanDifinition到ioc容器中,抽象类和接口都不会     */    Set<BeanDefinitionHolder> 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<BeanDefinitionHolder> 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<MapperFactoryBean>定义的注入模型是通过 包扫描进来的,所有我们的默认注入模型就是       * AutowireCapableBeanFactory.AUTOWIRE_NO=0注入模型为0的时候,在这种情况下,若我们的MapperFactoryBean       * 的字段属性是永远自动注入不了值的因为字段上是没有 @AutoWired注解,所以我们需要把UserMapper<MapperFactoryBean> 的bean定义的注入模型给改成我们的 AUTOWIRE_BY_TYPE       * = 1,指定这个类型就是根据类型装配的话, 第一:我们的字段上不需要写@AutoWired注解,为啥? springioc会把当前UserMapper<MapperFactoryBean>中的setXXX(入参)       * 都会去解析一次入参,入参的值可定会从ioc容器中获取,然后调用setXXX方法给赋值好. 或 AUTOWIRE_By_Type=1       * 指定这个类型就是根据类型装配的话,第一:我们的字段上不需要写@AutoWired注解,为啥? springioc会把当前UserMapper<MapperFactoryBean>中的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<MapperFactoryBean>的时候,会调用父类       * 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<T> {    @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<T> extends SqlSessionDaoSupport implements FactoryBean<T> {  private Class<T> mapperInterface;  private boolean addToConfig = true;  public MapperFactoryBean() {    // intentionally empty  }  /**   * 在这里又是一个牛逼的设计闪光点:我们知道在ClassPathMapperScanner 扫描我们的UserMapper<MapperFactoryBean>的时候做了一个牛逼的事情,   * 他扫描我们的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<T> mapperInterface) {    this.mapperInterface = mapperInterface;  }  /**   * 方法实现说明:在UserMapper<MapperFactoryBean> 父类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<Class<?>, 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<T> 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<T> mapperInterface) {    this.mapperInterface = mapperInterface;  }  /**   * Return the mapper interface of the MyBatis mapper   *   * @return class of the interface   */  public Class<T> 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.   * <p>   * If it is true, the mapper will be added to MyBatis in the case it is not already registered.   * <p>   * 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维护了一条<select|update|delete|insert>节点的封装;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方法;

总流程图

标签: #mybatis和spring整合原理