Mybatis之深入學(xué)習(xí)——跟進(jìn)中......

之前在spring mvc + mybatis項(xiàng)目中對(duì)mybatis的使用有了一定的掌握,但對(duì)于其內(nèi)部的具體實(shí)現(xiàn)并不了解季二,因此在此開(kāi)啟對(duì)于mybatis更加深入的學(xué)習(xí)檩咱。

一、介紹

定義

MyBatis 是一個(gè)可以自定義SQL胯舷、存儲(chǔ)過(guò)程和高級(jí)映射的持久層框架刻蚯。MyBatis 摒除了大部分的JDBC代碼、手工設(shè)置參數(shù)和結(jié)果集重獲桑嘶。
MyBatis 只使用簡(jiǎn)單的XML 和注解來(lái)配置和映射基本數(shù)據(jù)類(lèi)型炊汹、Map 接口和POJO 到數(shù)據(jù)庫(kù)記錄。相對(duì)Hibernate和Apache OJB等“一站式”O(jiān)RM解決方案而言逃顶,Mybatis 是一種“半自動(dòng)化”的ORM實(shí)現(xiàn)讨便。

核心組件

主要包括:
SqlSessionFactoryBuilder:會(huì)根據(jù)配置信息或代碼來(lái)生成SqlSessionFactory充甚;
SqlSessionFactory:依靠工廠來(lái)生成SqlSession;
SqlSession:是一個(gè)既可以發(fā)送SQL去執(zhí)行并返回結(jié)果器钟,也可以獲取Mapper的接口津坑;
SQL Mapper:是MyBatis新設(shè)計(jì)的組件,由一個(gè)Java接口和XML文件構(gòu)成傲霸,需要給出對(duì)應(yīng)的SQL和映射規(guī)則疆瑰。它負(fù)責(zé)發(fā)送SQL去執(zhí)行,并返回結(jié)果昙啄。

二穆役、架構(gòu)

MyBatis設(shè)計(jì)框架

API接口層:提供給外部使用的接口API,開(kāi)發(fā)人員通過(guò)這些本地API來(lái)操縱數(shù)據(jù)庫(kù)梳凛。接口層一接收到調(diào)用請(qǐng)求就會(huì)調(diào)用數(shù)據(jù)處理層來(lái)完成具體的數(shù)據(jù)處理耿币。
數(shù)據(jù)處理層:負(fù)責(zé)具體的SQL查找、SQL解析韧拒、SQL執(zhí)行和執(zhí)行結(jié)果映射處理等淹接。它主要的目的是根據(jù)調(diào)用的請(qǐng)求完成一次數(shù)據(jù)庫(kù)操作。
框架支撐層:負(fù)責(zé)最基礎(chǔ)的功能支撐叛溢,包括連接管理塑悼、事務(wù)管理、配置加載和緩存處理楷掉,這些都是共用的東西厢蒜,將他們抽取出來(lái)作為最基礎(chǔ)的組件。為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐烹植。
引導(dǎo)層:配置和啟動(dòng)MyBatis配置信息的方法斑鸦。

數(shù)據(jù)處理流程

數(shù)據(jù)處理流程

數(shù)據(jù)處理過(guò)程:
1.根據(jù)SQL的ID查找相應(yīng)的MappedStatement對(duì)象。
2.根據(jù)傳入?yún)?shù)對(duì)象解析MappedStatement對(duì)象草雕,得到最終要執(zhí)行的SQL和執(zhí)行傳入?yún)?shù)巷屿。
3.獲取數(shù)據(jù)庫(kù)連接,根據(jù)得到的最終SQL語(yǔ)句和執(zhí)行傳入?yún)?shù)到數(shù)據(jù)庫(kù)執(zhí)行墩虹,并得到執(zhí)行結(jié)果嘱巾。
4.根據(jù)MappedStatement對(duì)象中的結(jié)果映射對(duì)得到的執(zhí)行結(jié)果進(jìn)行轉(zhuǎn)換處理,并得到最終的處理結(jié)果败晴。
5.釋放連接資源。

三栽渴、源碼剖析

1.Mybatis Demo

以我mybatis入門(mén)的demo為例:

   private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
   private static SqlSessionFactory sqlSessionFactory;
   private static void init() throws IOException {
       String resource = "mybatis-config.xml";
       Reader reader = Resources.getResourceAsReader(resource);
       sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
       sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
   }

應(yīng)用程序的入口是SqlSessionFactoryBuilder尖坤,作用是通過(guò)XML配置文件創(chuàng)建Configuration對(duì)象,然后通過(guò)build方法創(chuàng)建SqlSessionFactory對(duì)象闲擦。
注:
沒(méi)有必要每次訪問(wèn)Mybatis就創(chuàng)建一次SqlSessionFactoryBuilder慢味,通常的做法是創(chuàng)建一個(gè)全局的對(duì)象

2. 入口類(lèi)SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {

    //Reader讀取mybatis配置文件场梆,傳入構(gòu)造方法
    public SqlSessionFactory build(Reader reader) {
        return build(reader, null, null);
    }

    public SqlSessionFactory build(Reader reader, String environment) {
        return build(reader, environment, null);
    }
  
    public SqlSessionFactory build(Reader reader, Properties properties) {
        return build(reader, null, properties);
    }
  
    //通過(guò)XMLConfigBuilder解析mybatis配置,從而創(chuàng)建SqlSessionFactory對(duì)象
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            //構(gòu)建的核心方法
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                reader.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }
}

可以看到構(gòu)建的核心是這一行:

return build(parser.parse());

parser的類(lèi)是XMLConfigBuilder纯路,XMLConfigBuilder 部分源碼如下:

/**
 * mybatis 配置文件解析
 */
public class XMLConfigBuilder extends BaseBuilder {
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }
  
    //外部調(diào)用此方法對(duì)mybatis配置文件進(jìn)行解析
    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        //從根節(jié)點(diǎn)configuration
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    /**此方法解析configuration節(jié)點(diǎn)下的子節(jié)點(diǎn)
    *在configuration下面能配置的節(jié)點(diǎn)為以下10個(gè)節(jié)點(diǎn)
    */
    private void parseConfiguration(XNode root) {
        try {
            propertiesElement(root.evalNode("properties")); 
            typeAliasesElement(root.evalNode("typeAliases"));
            pluginElement(root.evalNode("plugins"));
            objectFactoryElement(root.evalNode("objectFactory"));
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            settingsElement(root.evalNode("settings"));
            environmentsElement(root.evalNode("environments")); 
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            typeHandlerElement(root.evalNode("typeHandlers"));
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
}

跟進(jìn)中...

三或油、SQL執(zhí)行流程源碼剖析

我們都是通過(guò)SqlSession去執(zhí)行sql語(yǔ)句,Sqlsession對(duì)應(yīng)著一次數(shù)據(jù)庫(kù)會(huì)話驰唬。由于數(shù)據(jù)庫(kù)會(huì)話不是永久的顶岸,因此Sqlsession的生命周期也不應(yīng)該是永久的,相反叫编,在你每次訪問(wèn)數(shù)據(jù)庫(kù)時(shí)都需要?jiǎng)?chuàng)建辖佣。

獲取SqlSession的步驟:

  1. 首先,SqlSessionFactoryBuilder去讀取mybatis的配置文件搓逾;
  2. 然后構(gòu)建一個(gè)DefaultSqlSessionFactory卷谈。
    源碼如下:
/**
  * 一系列的構(gòu)造方法最終都會(huì)調(diào)用此構(gòu)建方法
  */
 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
   try {
     /**通過(guò)XMLConfigBuilder解析配置文件,
     *解析的配置相關(guān)信息都會(huì)被封裝為一個(gè)Configuration對(duì)象
     */
     XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
     //創(chuàng)建DefaultSessionFactory對(duì)象
     return build(parser.parse());
   } catch (Exception e) {
     throw ExceptionFactory.wrapException("Error building SqlSession.", e);
   } finally {
     ErrorContext.instance().reset();
     try {
       reader.close();
     } catch (IOException e) {
     }
   }
 }

 public SqlSessionFactory build(Configuration config) {
   return new DefaultSqlSessionFactory(config);
 }

在獲取到SqlSessionFactory之后霞篡,就可以通過(guò)SqlSessionFactory去獲取SqlSession對(duì)象:

 /**
  * 通常一系列openSession方法最終都會(huì)調(diào)用此方法
  */
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
   Transaction tx = null;
   try {
     /**通過(guò)Confuguration對(duì)象去獲取Mybatis相關(guān)配置信息, 
     *Environment對(duì)象包含了數(shù)據(jù)源和事務(wù)的配置
     */
     final Environment environment = configuration.getEnvironment();
     final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     //通過(guò)excutor真正執(zhí)行sql世蔗, excutor是對(duì)于Statement的封裝
     final Executor executor = configuration.newExecutor(tx, execType);
     //創(chuàng)建了一個(gè)DefaultSqlSession對(duì)象
     return new DefaultSqlSession(configuration, executor, autoCommit);
   } catch (Exception e) {
     closeTransaction(tx); // may have fetched a connection so lets call close()
     throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
   } finally {
     ErrorContext.instance().reset();
   }
 }

而方法openSessionFromDataSource才是實(shí)際創(chuàng)建SqlSession的地方:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {  
    Connection connection = null;  
    try {  
        final Environment environment = configuration.getEnvironment();  
        final DataSource dataSource = getDataSourceFromEnvironment(environment);  
         /**MyBatis對(duì)事務(wù)的處理相對(duì)簡(jiǎn)單,TransactionIsolationLevel中定義了幾種隔離級(jí)別朗兵,
         *并不支持內(nèi)嵌事務(wù)這樣較復(fù)雜的場(chǎng)景污淋,同時(shí)由于其是持久層的緣故,
         *所以真正在應(yīng)用開(kāi)發(fā)中會(huì)委托Spring來(lái)處理事務(wù)實(shí)現(xiàn)真正的與開(kāi)發(fā)者隔離矛市。
         *分析事務(wù)的實(shí)現(xiàn)是個(gè)入口芙沥,借此可以了解不少JDBC規(guī)范方面的事情。
         */
        TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);  
         connection = dataSource.getConnection();  
         if (level != null) {  
            connection.setTransactionIsolation(level.getLevel());
        }  
         connection = wrapConnection(connection);  
         Transaction tx = transactionFactory.newTransaction(connection,autoCommit);  
         Executorexecutor = configuration.newExecutor(tx, execType);  
         return newDefaultSqlSession(configuration, executor, autoCommit);  
     } catch (Exceptione) {  
        closeConnection(connection);  
        throwExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);  
    } finally {
        ErrorContext.instance().reset();
    }
}  

綜上浊吏,創(chuàng)建sqlsession的主要步驟:

  1. 從配置中獲取Environment访圃;
  2. 從Environment中取得DataSource;
  3. 從Environment中取得TransactionFactory箕昭;
  4. 從DataSource里獲取數(shù)據(jù)庫(kù)連接對(duì)象Connection叶雹;
  5. 在取得的數(shù)據(jù)庫(kù)連接上創(chuàng)建事務(wù)對(duì)象Transaction;
  6. 創(chuàng)建Executor對(duì)象(該對(duì)象非常重要墩衙,事實(shí)上sqlsession的所有操作都是通過(guò)它完成的)务嫡;
  7. 創(chuàng)建sqlsession對(duì)象。

在mybatis中漆改,通過(guò)MapperProxy動(dòng)態(tài)代理dao心铃, 也就是說(shuō), 當(dāng)執(zhí)行dao中的方法的時(shí)挫剑,其實(shí)是對(duì)應(yīng)的mapperProxy在代理去扣。
那么,接下來(lái)我們來(lái)看看是如何獲取MapperProxy對(duì)象:
首先樊破,通過(guò)SqlSession從Configuration中獲扔淅狻:

 /**
  * 什么都不做唆铐,直接調(diào)用configuration中的getMapper方法
  */
 @Override
 public <T> T getMapper(Class<T> type) {
   return configuration.<T>getMapper(type, this);
 }

之后,Configuration源碼:

/**
  * 直接調(diào)用MapperRegistry的方法
  */
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   return mapperRegistry.getMapper(type, sqlSession);
 }

MapperRegistry源碼如下:

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   //MapperProxyFactory動(dòng)態(tài)代理DAO接口
   final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
   if (mapperProxyFactory == null) {
     throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
   }
   try {
     //關(guān)鍵方法的實(shí)現(xiàn)
     return mapperProxyFactory.newInstance(sqlSession);
   } catch (Exception e) {
     throw new BindingException("Error getting mapper instance. Cause: " + e, e);
   }
 }

MapperProxyFactory源碼:

 protected T newInstance(MapperProxy<T> mapperProxy) {
   //動(dòng)態(tài)代理dao接口
   return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
 }
 
 public T newInstance(SqlSession sqlSession) {
   final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
   return newInstance(mapperProxy);
 }

上述解釋了是如何動(dòng)態(tài)代理DAO接口奔滑,接下來(lái)我們繼續(xù)來(lái)看具體是怎么執(zhí)行sql語(yǔ)句的艾岂,Sqlsession對(duì)數(shù)據(jù)庫(kù)的操作都是通過(guò)Executor來(lái)完成的。與Sqlsession一樣朋其,Executor也是動(dòng)態(tài)創(chuàng)建的:

 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {  
          executorType = executorType == null ? defaultExecutorType : executorType;  
          executorType = executorType == null ?ExecutorType.SIMPLE : executorType;  
          Executor executor;  
          /**如果不開(kāi)啟cache的話王浴,
          *創(chuàng)建的Executor只是3中基礎(chǔ)類(lèi)型之一
          */
          //BatchExecutor專(zhuān)門(mén)用于執(zhí)行批量sql操作
          if(ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this,transaction);
         } 
         //ReuseExecutor會(huì)重用statement執(zhí)行sql操作
          else if(ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this,transaction);  
        }
         //SimpleExecutor只是簡(jiǎn)單執(zhí)行sql
         else {  
            executor = newSimpleExecutor(this, transaction);
        }
          /**如果開(kāi)啟cache的話(默認(rèn)開(kāi)啟),
          *就會(huì)創(chuàng)建CachingExecutor令宿,它以前面創(chuàng)建的Executor作為唯一參數(shù)
          */
          if (cacheEnabled) {
           executor = new CachingExecutor(executor);  
        }
        executor = (Executor) interceptorChain.pluginAll(executor);  
        return executor;  
    }  

上述源碼中叼耙,CachingExecutor在查詢(xún)數(shù)據(jù)庫(kù)前先查找緩存,若沒(méi)找到的話調(diào)用delegate從數(shù)據(jù)庫(kù)查詢(xún)粒没,并將查詢(xún)結(jié)果存入緩存中筛婉。

上述中,每個(gè)MapperProxy對(duì)應(yīng)一個(gè)dao接口癞松, 在使用的時(shí)候爽撒,MapperProxy的具體實(shí)現(xiàn):

  /**
   * MapperProxy在執(zhí)行時(shí)會(huì)觸發(fā)此方法
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //MapperMethod執(zhí)行sqlSession
    return mapperMethod.execute(sqlSession, args);
  }

MapperMethod:

  1. 根據(jù)參數(shù)和返回值類(lèi)型選擇不同的sqlsession方法來(lái)執(zhí)行。
  2. 將mapper對(duì)象與sqlsession真正的關(guān)聯(lián)起來(lái)响蓉。

其execute方法源碼:

/**
   * 先判斷CRUD類(lèi)型硕勿,
   * 然后根據(jù)類(lèi)型去選擇到底執(zhí)行sqlSession中的哪個(gè)方法
   */
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    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()) {
        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;
  }

對(duì)sqlsession方法的訪問(wèn)最終都會(huì)落到executor的相應(yīng)方法上去。

SqlSession的CRUD方法枫甲,以selectList方法為例:

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      //CRUD實(shí)際上是交給Excecutor去處理
      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();
    }
  }

Executor分成兩大類(lèi)源武,一類(lèi)是CacheExecutor,另一類(lèi)是普通Executor想幻。
普通Executor:

  1. BatchExecutor專(zhuān)門(mén)用于執(zhí)行批量sql操作粱栖。
  2. ReuseExecutor會(huì)重用statement執(zhí)行sql操作。
  3. SimpleExecutor只是簡(jiǎn)單執(zhí)行sql沒(méi)有什么特別的脏毯。
    以SimpleExecutor為例:
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler) throws SQLException {  
    Statement stmt = null;  
    try {  
        Configuration configuration = ms.getConfiguration();  
        StatementHandler handler = configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);  
        stmt =prepareStatement(handler);  
        returnhandler.query(stmt, resultHandler);  
    } finally {  
        closeStatement(stmt);  
    }  
}  

通過(guò)一層一層的調(diào)用闹究,最終會(huì)來(lái)到doQuery方法,以SimpleExecutor為例:

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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler封裝了Statement, 通過(guò)StatementHandler 去處理
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

四食店、動(dòng)態(tài)SQL

什么是動(dòng)態(tài)SQL? 有什么作用渣淤?

傳統(tǒng)的使用JDBC的方法,在組合復(fù)雜的的SQL語(yǔ)句時(shí)吉嫩,需要拼接价认,容易導(dǎo)致錯(cuò)誤。Mybatis的動(dòng)態(tài)SQL功能正是為了解決這種問(wèn)題應(yīng)用而生自娩, 其通過(guò) if, choose, when, otherwise, trim, where, set, foreach標(biāo)簽用踩,可組合成非常靈活的SQL語(yǔ)句,從而提高開(kāi)發(fā)人員的效率。

if

<select id="findUserById" resultType="user">
    select * from user where 
        <if test="id != null">
               id=#{id}
        </if>
    and deleteFlag=0;
</select>

上面例子: 如果傳入的id 不為空捶箱, 那么才會(huì)SQL才拼接id = #{id}。
但如果傳入的id為null, 那么你這最終的SQL語(yǔ)句:

 select * from user where and deleteFlag=0

語(yǔ)句有錯(cuò)动漾,無(wú)法通過(guò)解析丁屎!
此時(shí)需要引入where

where

<select id="findUserById" resultType="user">
    select * from user 
        <where>
            <if test="id != null">
                id=#{id}
            </if>
            and deleteFlag=0;
        </where>
</select>

mybatis中,當(dāng)where標(biāo)簽遇到AND或OR時(shí)旱眯,會(huì)去除AND或OR晨川。

set

<update id="updateUser" parameterType="com.dy.entity.User">
    update user
        <set>
            <if test="name != null">name = #{name},</if> 
            <if test="password != null">password = #{password},</if> 
            <if test="age != null">age = #{age},</if> 
        </set>
        <where>
            <if test="id != null">
                id = #{id}
            </if>
            and deleteFlag = 0;
        </where>
</update>

foreach

java中有for, 可通過(guò)for循環(huán), 同樣在mybatis中有foreach, 可通過(guò)它實(shí)現(xiàn)循環(huán)删豺,循環(huán)的對(duì)象主要是java容器和數(shù)組共虑。

<select id="selectPostIn" resultType="domain.blog.Post">
    SELECT *
    FROM POST P
    WHERE ID in
    <foreach item="item" index="index" collection="list"
        open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

五、緩存機(jī)制源碼分析

1. 介紹

當(dāng)一條SQL語(yǔ)句被標(biāo)記為“可緩存”后呀页,第一次執(zhí)行時(shí)會(huì)將從數(shù)據(jù)庫(kù)獲取的所有數(shù)據(jù)存儲(chǔ)在一段高速緩存中妈拌,之后執(zhí)行同樣語(yǔ)句時(shí)會(huì)從高速緩存中讀取結(jié)果,而不是再次在數(shù)據(jù)庫(kù)中去命中蓬蝶。

Mybatis提供查詢(xún)緩存尘分,用于減輕數(shù)據(jù)壓力,提高數(shù)據(jù)庫(kù)性能丸氛。

Mybaits提供一級(jí)緩存培愁,和二級(jí)緩存:

  1. 一級(jí)緩存的作用域是同一個(gè)SqlSession,在同一個(gè)sqlSession中兩次執(zhí)行相同的sql語(yǔ)句缓窜,第一次執(zhí)行完畢會(huì)將數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)寫(xiě)到緩存(內(nèi)存)定续,第二次會(huì)從緩存中獲取數(shù)據(jù)將不再?gòu)臄?shù)據(jù)庫(kù)查詢(xún),從而提高查詢(xún)效率禾锤。當(dāng)一個(gè)sqlSession結(jié)束后該sqlSession中的一級(jí)緩存也就不存在了私股。
    Mybatis默認(rèn)開(kāi)啟一級(jí)緩存。

  2. 二級(jí)緩存是多個(gè)SqlSession共享的时肿,其作用域是mapper的同一個(gè)namespace庇茫,不同的sqlSession兩次執(zhí)行相同namespace下的sql語(yǔ)句且向sql中傳遞參數(shù)也相同即最終執(zhí)行相同的sql語(yǔ)句,第一次執(zhí)行完畢會(huì)將數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)寫(xiě)到緩存(內(nèi)存)螃成,第二次會(huì)從緩存中獲取數(shù)據(jù)將不再?gòu)臄?shù)據(jù)庫(kù)查詢(xún)旦签,從而提高查詢(xún)效率。

Mybatis中一級(jí)緩存和二級(jí)緩存的結(jié)構(gòu)如下:

Mybatis緩存機(jī)制

2. 源碼剖析

2.1 一級(jí)緩存

一級(jí)緩存的作用域是SqlSession寸宏,那么我們就先看從SqlSession入手宁炫,類(lèi)DefaultSqlSession是接口SqlSession的實(shí)現(xiàn)類(lèi), 其中方法selectList:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
   try {
     MappedStatement ms = configuration.getMappedStatement(statement);
     List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
     return result;
   } catch (Exception e) {
     throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
   } finally {
     ErrorContext.instance().reset();
   }
}

可以看到SqlSession調(diào)用接口Executor中的方法氮凝。接下來(lái)我們看下DefaultSqlSession中的executor接口屬性是如何得到的:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
   Transaction tx = null;
   try {
     final Environment environment = configuration.getEnvironment();
     final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
     return new DefaultSqlSession(configuration, executor);
   } catch (Exception e) {
     closeTransaction(tx); // may have fetched a connection so lets call close()
     throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
   } finally {
     ErrorContext.instance().reset();
   }
}

可以看到羔巢,Executor接口的實(shí)現(xiàn)類(lèi)是由Configuration構(gòu)造的:

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
   executorType = executorType == null ? defaultExecutorType : executorType;
   executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
   Executor executor;
   if (ExecutorType.BATCH == executorType) {
     executor = new BatchExecutor(this, transaction);
   } else if (ExecutorType.REUSE == executorType) {
     executor = new ReuseExecutor(this, transaction);
   } else {
     executor = new SimpleExecutor(this, transaction);
   }
   if (cacheEnabled) {
     executor = new CachingExecutor(executor, autoCommit);
   }
   executor = (Executor) interceptorChain.pluginAll(executor);
   return executor;
}

根據(jù)不同的ExecutorType創(chuàng)建Executor:

  1. 如果屬性cacheEnabled為true的話,那么通過(guò)裝飾器CachingExecutor包裝executor,這個(gè)裝飾器是 竿秆。
  2. 屬性cacheEnabled是配置文件中節(jié)點(diǎn)settings中子節(jié)點(diǎn)cacheEnabled的值启摄,默認(rèn)為true。
    接下來(lái)幽钢,CachingExecutor執(zhí)行sql的操作是什么歉备,類(lèi)CachingExecutor中方法query:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   //cache是個(gè)二級(jí)緩存
   Cache cache = ms.getCache();
   if (cache != null) {
     flushCacheIfRequired(ms);
     if (ms.isUseCache() && resultHandler == null) {
       ensureNoOutParams(ms, parameterObject, boundSql);
       if (!dirty) {
         cache.getReadWriteLock().readLock().lock();
         try {
           @SuppressWarnings("unchecked")
           List<E> cachedList = (List<E>) cache.getObject(key);
           if (cachedList != null) return cachedList;
         } finally {
           cache.getReadWriteLock().readLock().unlock();
         }
       }
       List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
       tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
       return list;
     }
   }
   return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

上述代碼中是類(lèi)SimpleExecutor,由于SimpleExecutor沒(méi)有覆蓋父類(lèi)中方法query匪燕,因此最終執(zhí)行了類(lèi)SimpleExecutor的父類(lèi)BaseExecutor中的方法query蕾羊。

由此可見(jiàn),一級(jí)緩存的核心就是類(lèi)BaseExecutor的方法query帽驯。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
   if (closed) throw new ExecutorException("Executor was closed.");
   if (queryStack == 0 && ms.isFlushCacheRequired()) {
     clearLocalCache();
   }
   List<E> list;
   try {
     queryStack++;
     //localCache就是一級(jí)緩存
     list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
     if (list != null) {
       handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
     } else {
       list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
   } finally {
     queryStack--;
   }
   if (queryStack == 0) {
     for (DeferredLoad deferredLoad : deferredLoads) {
       deferredLoad.load();
     }
     deferredLoads.clear(); // issue #601
     if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
       clearLocalCache(); // issue #482
     }
   }
   return list;
}

類(lèi)BaseExecutor中的屬性localCache是類(lèi)PerpetualCache的實(shí)例龟再。類(lèi)PerpetualCache 同樣實(shí)現(xiàn)了Mybatis的Cache緩存接口的實(shí)現(xiàn)類(lèi),內(nèi)部通過(guò)使用Map 類(lèi)型的屬性存儲(chǔ)緩存數(shù)據(jù)尼变。
localCache就是一級(jí)緩存利凑。
在執(zhí)行新增或更新或刪除操作,一級(jí)緩存就會(huì)被清除嫌术,接下來(lái)我們來(lái)看看其原理截碴。首先Mybatis在新增或刪除時(shí),都是通過(guò)調(diào)用方法update蛉威,即日丹,新增或刪除操作在Mybatis中都被視為更新操作。
類(lèi)DefaultSqlSession中方法update:

public int update(String statement, Object parameter) {
   try {
     dirty = true;
     MappedStatement ms = configuration.getMappedStatement(statement);
     //調(diào)用了CachingExecutor的update方法
     return executor.update(ms, wrapCollection(parameter));
   } catch (Exception e) {
     throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
   } finally {
     ErrorContext.instance().reset();
   }
}

調(diào)用了CachingExecutor的update方法蚯嫌。

public int update(MappedStatement ms, Object parameterObject) throws SQLException {
   //方法flushCacheIfRequired清除的是二級(jí)緩存
   flushCacheIfRequired(ms);
   return delegate.update(ms, parameterObject);
}

CachingExecutor委托給類(lèi)SimpleExecutor的方法update哲虾,SimpleExecutor沒(méi)有覆蓋父類(lèi)BaseExecutor的方法update。BaseExecutor的方法update:

public int update(MappedStatement ms, Object parameter) throws SQLException {
   ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
   if (closed) throw new ExecutorException("Executor was closed.");
   //清除一級(jí)緩存LocalCache
   clearLocalCache();
   return doUpdate(ms, parameter);
}

方法clearLocalCache清除一級(jí)緩存LocalCache:

public void clearLocalCache() {
   if (!closed) {
     localCache.clear();
     localOutputParameterCache.clear();
   }
}

可以看到:
如果sqlsession沒(méi)有關(guān)閉的話择示,進(jìn)行新增束凑、刪除、修改這類(lèi)更新操作栅盲,那么就清除一級(jí)緩存汪诉,即SqlSession的緩存。

2.2 二級(jí)緩存

二級(jí)緩存的作用域是全局谈秫,即扒寄,二級(jí)緩存已脫離SqlSession的控制,二級(jí)緩存在SqlSession關(guān)閉或提交之后才會(huì)生效拟烫。

二級(jí)緩存的工作機(jī)制:

  1. 一個(gè)SqlSession對(duì)象會(huì)通過(guò)使用一個(gè)Executor對(duì)象來(lái)完成會(huì)話操作该编,Mybatis的二級(jí)緩存機(jī)制的關(guān)鍵就在于這個(gè)Executor對(duì)象。
  2. 如果用戶(hù)配置了屬性"cacheEnabled=true"硕淑,那么Mybatis在為SqlSession的對(duì)象創(chuàng)建Executor對(duì)象時(shí)课竣,會(huì)對(duì)Executor對(duì)象加上裝飾器CachingExecutor嘉赎,此時(shí)SqlSession通過(guò)使用CachingExecutor對(duì)象完成操作請(qǐng)求。
  3. CachingExecutor對(duì)于查詢(xún)請(qǐng)求于樟,首先判斷該查詢(xún)請(qǐng)求在Application級(jí)別的二級(jí)緩存中是否有緩存結(jié)果公条。
    3.1如果有查詢(xún)結(jié)果,則直接返回緩存結(jié)果迂曲;
    3.2 如果緩存中沒(méi)有赃份,再交給真正的Executor對(duì)象來(lái)完成查詢(xún)操作,之后CachingExecutor會(huì)將真正Executor返回的查詢(xún)結(jié)果放置到緩存中奢米,最后再返回給用戶(hù)。

下圖是二級(jí)緩存工作模式:


二級(jí)緩存工作模式

緩存配置操作:

  1. mybatis全局配置文件中的setting中的cacheEnabled需為true纠永。
  2. mapper配置文件中需要加入<cache>節(jié)點(diǎn)鬓长。
  3. mapper配置文件中的select節(jié)點(diǎn)需要加上屬性u(píng)seCache需要為true。

類(lèi)XMLMappedBuilder用來(lái)解析每個(gè)mapper配置文件的解析類(lèi)尝江,每一個(gè)mapper配置都會(huì)實(shí)例化一個(gè)XMLMapperBuilder類(lèi)涉波,其中的解析方法:

private void configurationElement(XNode context) {
   try {
     String namespace = context.getStringAttribute("namespace");
     if (namespace.equals("")) {
         throw new BuilderException("Mapper's namespace cannot be empty");
     }
     builderAssistant.setCurrentNamespace(namespace);
     cacheRefElement(context.evalNode("cache-ref"));
     //解析緩存cache方法
     cacheElement(context.evalNode("cache"));
     parameterMapElement(context.evalNodes("/mapper/parameterMap"));
     resultMapElements(context.evalNodes("/mapper/resultMap"));
     sqlElement(context.evalNodes("/mapper/sql"));
     buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
   } catch (Exception e) {
     throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
   }
}

方法cacheElement解析緩存cache:

private void cacheElement(XNode context) throws Exception {
   if (context != null) {
     String type = context.getStringAttribute("type", "PERPETUAL");
     Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
     String eviction = context.getStringAttribute("eviction", "LRU");
     Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
     Long flushInterval = context.getLongAttribute("flushInterval");
     Integer size = context.getIntAttribute("size");
     boolean readWrite = !context.getBooleanAttribute("readOnly", false);
     Properties props = context.getChildrenAsProperties();
     builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
   }
}

解析完cache標(biāo)簽之后會(huì)使用類(lèi)builderAssistant的userNewCache方法:

public Cache useNewCache(Class<? extends Cache> typeClass,
    Class<? extends Cache> evictionClass,
    Long flushInterval,
    Integer size,
    boolean readWrite,
    Properties props) {
        typeClass = valueOrDefault(typeClass, PerpetualCache.class);
        evictionClass = valueOrDefault(evictionClass, LruCache.class);
        Cache cache = new CacheBuilder(currentNamespace)
       .implementation(typeClass)
       .addDecorator(evictionClass)
       .clearInterval(flushInterval)
       .size(size)
       .readWrite(readWrite)
       .properties(props)
       .build();
   configuration.addCache(cache);
   currentCache = cache;
   return cache;
}

目前,mapper配置文件中的cache節(jié)點(diǎn)被解析到了XMLMapperBuilder實(shí)例中的builderAssistant屬性中的currentCache值里炭序。
接下來(lái)類(lèi)XMLMapperBuilder會(huì)解析節(jié)點(diǎn)select啤覆,通過(guò)使用XMLStatementBuilder進(jìn)行解析(也包括其他節(jié)點(diǎn)insert,update惭聂,delete):

public void parseStatementNode() {
   String id = context.getStringAttribute("id");
   String databaseId = context.getStringAttribute("databaseId");

   if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;

   Integer fetchSize = context.getIntAttribute("fetchSize");
   Integer timeout = context.getIntAttribute("timeout");
   String parameterMap = context.getStringAttribute("parameterMap");
   String parameterType = context.getStringAttribute("parameterType");
   Class<?> parameterTypeClass = resolveClass(parameterType);
   String resultMap = context.getStringAttribute("resultMap");
   String resultType = context.getStringAttribute("resultType");
   String lang = context.getStringAttribute("lang");
   LanguageDriver langDriver = getLanguageDriver(lang);

   Class<?> resultTypeClass = resolveClass(resultType);
   String resultSetType = context.getStringAttribute("resultSetType");
   StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
   ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

   String nodeName = context.getNode().getNodeName();
   SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
   boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
   boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
   boolean useCache = context.getBooleanAttribute("useCache", isSelect);
   boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

   XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
   includeParser.applyIncludes(context.getNode());

   // 解析selectKey
   processSelectKeyNodes(id, parameterTypeClass, langDriver);

   // 解析SQL
   SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
   String resultSets = context.getStringAttribute("resultSets");
   String keyProperty = context.getStringAttribute("keyProperty");
   String keyColumn = context.getStringAttribute("keyColumn");
   KeyGenerator keyGenerator;
   String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
   keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
   if (configuration.hasKeyGenerator(keyStatementId)) {
     keyGenerator = configuration.getKeyGenerator(keyStatementId);
   } else {
     keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
         configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
         ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
   }

   builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
       fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
       resultSetTypeEnum, flushCache, useCache, resultOrdered,
       keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

上述源碼前半部分都在解析一些標(biāo)簽的屬性窗声,可以看到最后一行使用builderAssistant添加MappedStatement,其中builderAssistant屬性是構(gòu)造XMLStatementBuilder的時(shí)候通過(guò)XMLMappedBuilder傳入的辜纲,接下來(lái)笨觅,我們看如何設(shè)置二級(jí)緩存:

private void setStatementCache(
    boolean isSelect,
    boolean flushCache,
    boolean useCache,
    Cache cache,
    MappedStatement.Builder statementBuilder) {
        flushCache = valueOrDefault(flushCache, !isSelect);
        useCache = valueOrDefault(useCache, isSelect);
        statementBuilder.flushCacheRequired(flushCache);
        statementBuilder.useCache(useCache);
        statementBuilder.cache(cache);
}

最終mapper配置文件中的<cache/>被設(shè)置到了類(lèi)XMLMapperBuilder的屬性builderAssistant中,XMLMapperBuilder中使用XMLStatementBuilder遍歷CRUD節(jié)點(diǎn)耕腾,遍歷CRUD節(jié)點(diǎn)的時(shí)候?qū)⑦@個(gè)cache節(jié)點(diǎn)設(shè)置到這些CRUD節(jié)點(diǎn)中见剩,這個(gè)cache就是所謂的二級(jí)緩存。
在使用二級(jí)緩存之后:查詢(xún)數(shù)據(jù)的話扫俺,先從二級(jí)緩存中拿數(shù)據(jù)苍苞,如果沒(méi)有的話,去一級(jí)緩存中拿狼纬,一級(jí)緩存也沒(méi)有的話再查詢(xún)數(shù)據(jù)庫(kù)羹呵。有了數(shù)據(jù)之后在丟到TransactionalCache這個(gè)對(duì)象的entriesToAddOnCommit屬性中。

接下來(lái)我們來(lái)驗(yàn)證為什么SqlSession commit或close之后疗琉,二級(jí)緩存才會(huì)生效:
類(lèi)DefaultSqlSession的方法commit:

public void commit(boolean force) {
   try {
     executor.commit(isCommitOrRollbackRequired(force));
     dirty = false;
   } catch (Exception e) {
     throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
   } finally {
     ErrorContext.instance().reset();
   }
}

類(lèi)CachingExecutor的方法commit:

public void commit(boolean required) throws SQLException {
   delegate.commit(required);
   tcm.commit();
   dirty = false;
}

類(lèi)TransactionalCacheManager的方法commit:

public void commit() {
   for (TransactionalCache txCache : transactionalCaches.values()) {
     txCache.commit();
   }
}

類(lèi)TransactionalCache的方法commit:

public void commit() {
   delegate.getReadWriteLock().writeLock().lock();
   try {
     if (clearOnCommit) {
       delegate.clear();
     } else {
       for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
         entry.commit();
       }
     }
     for (AddEntry entry : entriesToAddOnCommit.values()) {
       entry.commit();
     }
     reset();
   } finally {
     delegate.getReadWriteLock().writeLock().unlock();
   }
}

可以看到調(diào)用了AddEntry的方法commit:

public void commit() {
    cache.putObject(key, value);
}

原來(lái)方法AddEntry中的commit方法會(huì)把數(shù)據(jù)丟到cache中担巩,也就是丟到二級(jí)緩存中。
而之所以為何調(diào)用close方法后没炒,二級(jí)緩存才會(huì)生效涛癌,是因?yàn)閏lose方法內(nèi)部會(huì)調(diào)用commit方法犯戏。

四、JDBC演變到Mybatis過(guò)程

JDBC實(shí)現(xiàn)查詢(xún)所需步驟:

加載JDBC驅(qū)動(dòng)拳话;
建立并獲取數(shù)據(jù)庫(kù)連接先匪;
創(chuàng)建 JDBC Statements 對(duì)象;
設(shè)置SQL語(yǔ)句的傳入?yún)?shù)弃衍;
執(zhí)行SQL語(yǔ)句并獲得查詢(xún)結(jié)果呀非;
對(duì)查詢(xún)結(jié)果進(jìn)行轉(zhuǎn)換處理并將處理結(jié)果返回;
釋放相關(guān)資源(關(guān)閉Connection镜盯,關(guān)閉Statement岸裙,關(guān)閉ResultSet);

1. 連接獲取和釋放

問(wèn)題描述:
數(shù)據(jù)庫(kù)連接頻繁的開(kāi)啟和關(guān)閉本身就造成了資源的浪費(fèi)速缆,影響系統(tǒng)的性能降允。

優(yōu)化方案:
數(shù)據(jù)庫(kù)連接的獲取和關(guān)閉我們可以使用數(shù)據(jù)庫(kù)連接池來(lái)解決資源浪費(fèi)的問(wèn)題。通過(guò)連接池就可以反復(fù)利用已經(jīng)建立的連接去訪問(wèn)數(shù)據(jù)庫(kù)了艺糜。減少連接的開(kāi)啟和關(guān)閉的時(shí)間剧董。

2. SQL統(tǒng)一存取

問(wèn)題描述:
使用JDBC進(jìn)行操作數(shù)據(jù)庫(kù)時(shí),SQL語(yǔ)句基本都散落在各個(gè)JAVA類(lèi)中破停,這樣有三個(gè)不足之處:

  1. 可讀性很差翅楼,不利于維護(hù)以及做性能調(diào)優(yōu)。
  2. 改動(dòng)Java代碼需要重新編譯真慢、打包部署毅臊。
  3. 不利于取出SQL在數(shù)據(jù)庫(kù)客戶(hù)端執(zhí)行。

優(yōu)化方案:
可以考慮不把SQL語(yǔ)句寫(xiě)到Java代碼中黑界,那么把SQL語(yǔ)句放到哪里呢褂微?首先需要有一個(gè)統(tǒng)一存放的地方,我們可以將這些SQL語(yǔ)句統(tǒng)一集中放到配置文件或者數(shù)據(jù)庫(kù)里面(以key-value的格式存放)园爷。然后通過(guò)SQL語(yǔ)句的key值去獲取對(duì)應(yīng)的SQL語(yǔ)句宠蚂。

3. 傳入?yún)?shù)映射和動(dòng)態(tài)SQL

問(wèn)題描述:
既然我們已經(jīng)把SQL語(yǔ)句統(tǒng)一存放在配置文件或者數(shù)據(jù)庫(kù)中了,怎么做到能夠根據(jù)前臺(tái)傳入?yún)?shù)的不同童社,動(dòng)態(tài)生成對(duì)應(yīng)的SQL語(yǔ)句呢求厕?

優(yōu)化方案:
需要使用一種有別于SQL的語(yǔ)法來(lái)嵌入變量(比如使用#變量名#)。這樣扰楼,SQL語(yǔ)句經(jīng)過(guò)解析后就可以動(dòng)態(tài)的生成符合上下文的SQL語(yǔ)句呀癣。可以使用#變量名#表示占位符變量弦赖,使用變量名表示非占位符變量项栏。

4. 結(jié)果映射和結(jié)果緩存

問(wèn)題描述:
執(zhí)行SQL語(yǔ)句、獲取執(zhí)行結(jié)果蹬竖、對(duì)執(zhí)行結(jié)果進(jìn)行轉(zhuǎn)換處理沼沈、釋放相關(guān)資源是一整套下來(lái)的流酬。假如是執(zhí)行查詢(xún)語(yǔ)句,那么執(zhí)行SQL語(yǔ)句后列另,返回的是一個(gè)ResultSet結(jié)果集芽腾,這個(gè)時(shí)候我們就需要將ResultSet對(duì)象的數(shù)據(jù)取出來(lái),不然等到釋放資源時(shí)就取不到這些結(jié)果信息了页衙。

優(yōu)化方案:
必須告訴SQL處理器兩點(diǎn):第一摊滔,需要返回什么類(lèi)型的對(duì)象;第二店乐,需要返回的對(duì)象的數(shù)據(jù)結(jié)構(gòu)怎么跟執(zhí)行的結(jié)果映射艰躺,這樣才能將具體的值copy到對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)上。

5. 解決重復(fù)SQL語(yǔ)句問(wèn)題

問(wèn)題描述:
由于我們將所有SQL語(yǔ)句都放到配置文件中眨八,這個(gè)時(shí)候會(huì)遇到一個(gè)SQL重復(fù)的問(wèn)題腺兴,幾個(gè)功能的SQL語(yǔ)句其實(shí)都差不多,有些可能是SELECT后面那段不同踪古、有些可能是WHERE語(yǔ)句不同。有時(shí)候表結(jié)構(gòu)改了券腔,那么我們就需要改多個(gè)地方伏穆,不利于維護(hù)。

優(yōu)化方案:
當(dāng)我們的代碼程序出現(xiàn)重復(fù)代碼時(shí)怎么辦纷纫?將重復(fù)的代碼抽離出來(lái)成為獨(dú)立的一個(gè)類(lèi)枕扫,然后在各個(gè)需要使用的地方進(jìn)行引用。對(duì)于SQL重復(fù)的問(wèn)題辱魁,我們也可以采用這種方式烟瞧,通過(guò)將SQL片段模塊化,將重復(fù)的SQL片段獨(dú)立成一個(gè)SQL塊染簇,然后在各個(gè)SQL語(yǔ)句引用重復(fù)的SQL塊参滴,這樣需要修改時(shí)只需要修改一處即可。

未完跟進(jìn)中......

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锻弓,一起剝皮案震驚了整個(gè)濱河市砾赔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌青灼,老刑警劉巖暴心,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異杂拨,居然都是意外死亡专普,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)弹沽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)檀夹,“玉大人筋粗,你說(shuō)我怎么就攤上這事』魇ぃ” “怎么了亏狰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)偶摔。 經(jīng)常有香客問(wèn)我暇唾,道長(zhǎng),這世上最難降的妖魔是什么辰斋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任策州,我火速辦了婚禮,結(jié)果婚禮上宫仗,老公的妹妹穿的比我還像新娘够挂。我一直安慰自己,他們只是感情好藕夫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布孽糖。 她就那樣靜靜地躺著,像睡著了一般毅贮。 火紅的嫁衣襯著肌膚如雪办悟。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天滩褥,我揣著相機(jī)與錄音病蛉,去河邊找鬼。 笑死瑰煎,一個(gè)胖子當(dāng)著我的面吹牛铺然,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播酒甸,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼魄健,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了插勤?” 一聲冷哼從身側(cè)響起诀艰,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饮六,沒(méi)想到半個(gè)月后其垄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卤橄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年绿满,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窟扑。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喇颁,死狀恐怖漏健,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情橘霎,我是刑警寧澤蔫浆,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站姐叁,受9級(jí)特大地震影響瓦盛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜外潜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一原环、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧处窥,春花似錦嘱吗、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至哆致,卻和暖如春绕德,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沽瞭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工迁匠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剩瓶,地道東北人驹溃。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像延曙,于是被迫代替她去往敵國(guó)和親豌鹤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容