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();
        }
    }
复制代码

\

//获取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;
//  <?xml version="1.0" encoding="UTF-8" ?> 
//  <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
//  "http://mybatis.org/dtd/mybatis-3-config.dtd"> 
//  <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为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> 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代理类的流程:

  1. 从sqlSession中获取mapper
  2. 从configration中获取mapper
  3. 从MapperRegistry中获取mapper
  4. 从knownMappers中获取mapper的动态代理类工厂
  5. 用工厂生成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中重要的几个类:

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

总流程图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值