zoukankan      html  css  js  c++  java
  • mybatis原理解析

    本文是结合spring-mybatis整合进行的分析

    1、先看看依赖的jar包:

            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.2.8</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>1.2.2</version>
            </dependency>        

    2、mybatis主要两个关键对象时SqlSessionFactory和SqlSession,接下来主要结合源码对这两个对象流程进行分析:

    在分析这两个对象之前先来看看XML配置情况:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
           default-autowire="byName" default-lazy-init="true">
    
        <!-- DataSource数据 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <property name="name" value="souchecar"/>
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
            <property name="maxActive" value="20"/>
            <property name="minIdle" value="2"/>
            <property name="initialSize" value="2"/>
            <property name="validationQuery" value="SELECT 1"/>
            <property name="testOnBorrow" value="false"/>
            <property name="testOnReturn" value="false"/>
            <property name="testWhileIdle" value="true"/>
            <property name="timeBetweenEvictionRunsMillis" value="60000"/>
            <property name="minEvictableIdleTimeMillis" value="300000"/>
            <property name="defaultAutoCommit" value="true"/>
            <property name="removeAbandoned" value="true"/>
            <property name="removeAbandonedTimeout" value="60"/>
            <property name="logAbandoned" value="true"/>
            <property name="filters" value="stat"/>
        </bean>
    
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- <property name="configLocation" value="classpath:Configuration.xml" /> -->
            <property name="mapperLocations">
                <list>
                    <value>classpath*:sqlmap/**/*.xml</value>
                </list>
            </property>
            <property name="dataSource" ref="dataSource"/>
    
        </bean>
    
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
            <constructor-arg index="0" ref="sqlSessionFactory"/>
        </bean>
        <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
    
    </beans>

    在配置中主要包含了数据源DruidDataSource,SqlSessionFactoryBean(包含了mybatis的映射文件),事物DataSourceTransactionManager,以及SqlSessionTemplate信息的配置,这里就不对具体的配置作用做过多的介绍

    3、SqlSessionFactory

      1)SqlSessionFactory对象是mybatis中的核心对象之一,主要是通过SqlSessionFactory来创建SqlSession对象,一般在一个数据库中,最好采用单例的模式,将SqlSessionFactory创建成一个单例的对象;

      2)结合以上的配置和源码来分析SqlSessionFactory创建的一个过程:在配置文件中,主要是通过SqlSessionFactoryBean来管理SqlSessionFactory创建过程,因为该类实现了InitializingBean接口,所以在spring初始化改bean的时候,会先执行InitializingBean接口中的afterPropertiesSet()方法,在该方法中会去调用buildSqlSessionFactory()方法,该方法是用来创建Configuration对象,将配置文件中配置项信息加载到该对象中,然后在根据Configuration创建SqlSessionFactory,如下:

      public void afterPropertiesSet() throws Exception {
        notNull(dataSource, "Property 'dataSource' is required");
        notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    
        this.sqlSessionFactory = buildSqlSessionFactory();
      }
    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
        Configuration configuration;
    
        XMLConfigBuilder xmlConfigBuilder = null;
        if (this.configLocation != null) {
          xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
          configuration = xmlConfigBuilder.getConfiguration();
        } else {
          if (logger.isDebugEnabled()) {
            logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
          }
          configuration = new Configuration();
          configuration.setVariables(this.configurationProperties);
        }
    
        if (this.objectFactory != null) {
          configuration.setObjectFactory(this.objectFactory);
        }
    
        if (this.objectWrapperFactory != null) {
          configuration.setObjectWrapperFactory(this.objectWrapperFactory);
        }
    
        if (hasLength(this.typeAliasesPackage)) {
          String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
          for (String packageToScan : typeAliasPackageArray) {
            configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                    typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
            if (logger.isDebugEnabled()) {
              logger.debug("Scanned package: '" + packageToScan + "' for aliases");
            }
          }
        }
    
        if (!isEmpty(this.typeAliases)) {
          for (Class<?> typeAlias : this.typeAliases) {
            configuration.getTypeAliasRegistry().registerAlias(typeAlias);
            if (logger.isDebugEnabled()) {
              logger.debug("Registered type alias: '" + typeAlias + "'");
            }
          }
        }
    
        if (!isEmpty(this.plugins)) {
          for (Interceptor plugin : this.plugins) {
            configuration.addInterceptor(plugin);
            if (logger.isDebugEnabled()) {
              logger.debug("Registered plugin: '" + plugin + "'");
            }
          }
        }
    
        if (hasLength(this.typeHandlersPackage)) {
          String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
          for (String packageToScan : typeHandlersPackageArray) {
            configuration.getTypeHandlerRegistry().register(packageToScan);
            if (logger.isDebugEnabled()) {
              logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
            }
          }
        }
    
        if (!isEmpty(this.typeHandlers)) {
          for (TypeHandler<?> typeHandler : this.typeHandlers) {
            configuration.getTypeHandlerRegistry().register(typeHandler);
            if (logger.isDebugEnabled()) {
              logger.debug("Registered type handler: '" + typeHandler + "'");
            }
          }
        }
    
        if (xmlConfigBuilder != null) {
          try {
            xmlConfigBuilder.parse();
    
            if (logger.isDebugEnabled()) {
              logger.debug("Parsed configuration file: '" + this.configLocation + "'");
            }
          } catch (Exception ex) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
          } finally {
            ErrorContext.instance().reset();
          }
        }
    
        if (this.transactionFactory == null) {
          this.transactionFactory = new SpringManagedTransactionFactory();
        }
    
        Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
        configuration.setEnvironment(environment);
    
        if (this.databaseIdProvider != null) {
          try {
            configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
          } catch (SQLException e) {
            throw new NestedIOException("Failed getting a databaseId", e);
          }
        }
    
        if (!isEmpty(this.mapperLocations)) {
          for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
              continue;
            }
         // 1.获取配置中映射mapper标签中的信息,存储到MappedStatement对象中,并sql存储到SqlSource对象中 
    try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (logger.isDebugEnabled()) { logger.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (logger.isDebugEnabled()) { logger.debug("Property 'mapperLocations' was not specified or no matching resources found"); } }   // 2.创建SqlSessionFactory对象
    return this.sqlSessionFactoryBuilder.build(configuration); }

      在该方法中先来分析一下xmlMapperBuilder.parse()方法,这个方法主要是将映射文件中的信息存储到相应的对象中:

      public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper")); //我们关注的是这个方法
          configuration.addLoadedResource(resource);
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingChacheRefs();
        parsePendingStatements();
      }

      在方法中configurationElement()主要是用来解析mapper标签中的信息,我们主要关注的是这个方法,下来的方法是用来解析映射文件中其他属性信息的

     1   private void configurationElement(XNode context) {
     2     try {
     3       String namespace = context.getStringAttribute("namespace");
     4       if (namespace.equals("")) {
     5           throw new BuilderException("Mapper's namespace cannot be empty");
     6       }
     7       builderAssistant.setCurrentNamespace(namespace);
     8       cacheRefElement(context.evalNode("cache-ref"));
     9       cacheElement(context.evalNode("cache"));
    10       parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    11       resultMapElements(context.evalNodes("/mapper/resultMap"));
    12       sqlElement(context.evalNodes("/mapper/sql"));
    13       buildStatementFromContext(context.evalNodes("select|insert|update|delete")); //我们关注的是这个方法
    14     } catch (Exception e) {
    15       throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    16     }
    17   }

      在configurationElement()方法中用来解析命名空间,参数集,结果集,select,insert,update,delete,标签等等,我们关注的是buildStatementFromContext(context.evalNodes("select|insert|update|delete"))方法

     1   private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
     2     for (XNode context : list) {
     3       final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
     4       try {
     5         statementParser.parseStatementNode();
     6       } catch (IncompleteElementException e) {
     7         configuration.addIncompleteStatement(statementParser);
     8       }
     9     }
    10   }

      在parseStatementNode()方法中,主要是用来将映射文件中的配置信息存储到MappedStatement对象中去,并将sql语句存储到了SqlSource对象中,到时候执行sql的时候,直接从该对象中获取;

    4、在配置文件中sqlSession使用的是SqlSessionTemplate,实现了SqlSession接口,就是一个SqlSession模板类,在加载<bean id = sqlSession>标签时,会去调用SqlSessionTemplate构造方法,SqlSessionTemplate类有三个构造方法,最终会调用

    参数sqlSessionFactory是SqlSessionFactoryBean初始化时生成的DefaultSqlSessionFactory,参数executorType是自己传入的,默认是simple,在这个构造方法中会初始换sqlSession(标记部分),通过动态代理生成SqlSession代理类,这里重点介绍

    SqlSessionInterceptor,这个是SqlSessionTemplate类中的内部类,实现了InvokerHandler,是一个代理类,所以生成的SqlSession代理类,每个方法都会去执行SqlSessionInterceptor中的invoke()方法,如下所示:

     

    首先会去初始化sqlSession对象,在getSqlSession(sessionFactory, executorType, exceptionTranslator)方法中会去调用sessionFactory中的openSession(executorType)方法,又通过调用openSessionFromDataSource(execType, level, autoCommit)方法,在该方法中进行初始化,对事物对象,executor进行初始化(Configuration.newExecutor(tx, execType)根据传入的类型创建对应的Executor对象),并创建且返回defaultSqlSession对象;executor对象对接下来sql的执行很重要,会在下一节进行介绍;

    5、SqlSession

      1)SqlSession主要有四大核心组件对象:Executor,StatementHandler,ParameterHandler,ResultSetHandler

      2)在分析这四个组件对象之前,先介绍一下,为什么在使用mybatis,只需要写映射文件对应的接口类,不需要写接口的实现类,这是因为mybatis使用Java中的动态代理,在SqlSession.getMapper(Class<T> type)方法时,本质是调用Configuration类下getMapper(Class<T> type, SqlSession sqlSession),调用关系如下:

    SqlSession.getMapper——>Configuration.getMapper——>MapperRegistry.getMapper——>MapperProxyFactory.newInstance

    在MapperProxyFactory.newInstance方法中,会使用代理类MapperProxy为该接口生成一个代理对象,在代理类中的每个方法中都会调用MapperProxy类中的invoke()方法,在invoke()方法中会为每个方法生成一个MapperMethod对象,在去调用MapperMethod类中execute()方法,这个其实就是SqlSession的入口;

     1 public class MapperProxyFactory<T> {
     2 
     3   private final Class<T> mapperInterface;
     4   private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
     5 
     6   public MapperProxyFactory(Class<T> mapperInterface) {
     7     this.mapperInterface = mapperInterface;
     8   }
     9 
    10   public Class<T> getMapperInterface() {
    11     return mapperInterface;
    12   }
    13 
    14   public Map<Method, MapperMethod> getMethodCache() {
    15     return methodCache;
    16   }
    17 
    18   @SuppressWarnings("unchecked")
    19   protected T newInstance(MapperProxy<T> mapperProxy) {
    20     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    21   }
    22 
    23   public T newInstance(SqlSession sqlSession) {
    24     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    25     return newInstance(mapperProxy);
    26   }
    27 
    28 }
     1 public class MapperProxy<T> implements InvocationHandler, Serializable {
     2 
     3   private static final long serialVersionUID = -6424540398559729838L;
     4   private final SqlSession sqlSession;
     5   private final Class<T> mapperInterface;
     6   private final Map<Method, MapperMethod> methodCache;
     7 
     8   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
     9     this.sqlSession = sqlSession;
    10     this.mapperInterface = mapperInterface;
    11     this.methodCache = methodCache;
    12   }
    13 
    14   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    15     if (Object.class.equals(method.getDeclaringClass())) {
    16       try {
    17         return method.invoke(this, args);
    18       } catch (Throwable t) {
    19         throw ExceptionUtil.unwrapThrowable(t);
    20       }
    21     }
    22     final MapperMethod mapperMethod = cachedMapperMethod(method);
    23     return mapperMethod.execute(sqlSession, args);
    24   }
    25 
    26   private MapperMethod cachedMapperMethod(Method method) {
    27     MapperMethod mapperMethod = methodCache.get(method);
    28     if (mapperMethod == null) {
    29       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
    30       methodCache.put(method, mapperMethod);
    31     }
    32     return mapperMethod;
    33   }
    34 
    35 }

    3)在execute方法中会根据sql类型,选择相应的方法进行执行,其实本质是调用SqlSession接口中相应的方法,SqlSession有三个实现类,其实最后本质还是调用的是DefaultSqlSession中的实现方法(在上节中介绍会初始化DefaultSqlSession对象),底层的调用是Executor的query()方法,调用的是实现类BaseExecutor的query()方法,在该方法中调用的是doQuery()方法;Executor有三个实现类,SimpleExecutor(默认)、ReuseExecutor、BatchExecutor;具体使用哪个在初始化SqlSession时确定,参考上节;在接下来的例子主要是以SimpleExecutor类举例:

     1   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     2     Statement stmt = null;
     3     try {
     4       Configuration configuration = ms.getConfiguration();
     5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     6       stmt = prepareStatement(handler, ms.getStatementLog());
     7       return handler.<E>query(stmt, resultHandler);
     8     } finally {
     9       closeStatement(stmt);
    10     }
    11   }

    4)StatementHandler

      在doquery()方法中会创建StatementHandler对象,并调用prepareStatement对sql进行预编译和参数初始化,在prepareStatement方法中又通过调用StatementHandler接口中的prepare()创建Statement对象和调用parameterize()方法进行参数初始化和sql预编译;

      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection);
        handler.parameterize(stmt);
        return stmt;
      }

    5)ParameterHandler

      在调用parameterize()方法,底层是通过ParameterHandler.setParameters(statement)方法进行参数的初始化过程;具体的实现过程可以参考DefaultParameterHandler.setParameters(statement)方法;

    6)ResultSetHandler

      通过StatementHandler.query()方法会将sql执行返回的结果进行封装,根据配置的resultSetType类型进行转换,具体实现可以参考DefaultResultSetHandler类中的handleResultSets方法;

  • 相关阅读:
    函数part1 初始函数
    文件操作 文件修改的两种方式
    文件操作part3 文件内指针的移动
    文件操作part2
    day08作业
    文件操作part1
    字符编码
    ORA-15124 数据库启动阶段报错
    将数据库从普通文件系统迁移到ASM中
    在ASM中移动数据文件
  • 原文地址:https://www.cnblogs.com/yxfmp426756/p/8882852.html
Copyright © 2011-2022 走看看