mybatis原理分析學習記錄,mybatis動態sql學習記錄

如下我的學習筆記,僅供參考,歡迎指正。java

MyBatis 是支持定製化 SQL、存儲過程以及高級映射的持久層框架,其主要就完成2件事情:mysql

  • 封裝JDBC操做spring

  • 利用反射打通Java類與SQL語句之間的相互轉換sql

MyBatis的主要設計目的就是讓咱們對執行SQL語句時對輸入輸出的數據管理更加方便,因此方便地寫出SQL和方便地獲取SQL的執行結果纔是MyBatis的核心競爭力。數據庫

MyBatis的配置

 spring整合mybatis(druid數據源)設計模式

  1 (1)整合思路:把MyBatis框架中使用所涉及的核心組件配置到Spring容器中
  2 (2)步驟:
  3 -->1.添加pom依賴,mybatis-spring,spring-tx,spring-jdbc
  4 -->2.建立實體類(entity)
  5 -->3.建立數據訪問接口(dao層的接口)
  6 -->4.配置SQL映射文件(resource下的mapper.xml文件)
  7 -->5.配置mybatis的配置文件(resource下的spring下的mybatis-config.xml)
  8 -->6.凡是使用了註解,都須要配置掃描註解定義的業務Bean: <context:component-scan base-package="com.one.ssm.dao"/>
  9 和<context:annotation-config/>(用於激活那些已經在spring容器裏註冊過的bean)
 10 
 11 
 12 <?xml version="1.0" encoding="UTF-8" ?>
 13 <!DOCTYPE configuration
 14         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 15         "http://mybatis.org/dtd/mybatis-3-config.dtd">
 16 <configuration>
 17 <!--配置全局屬性-->
 18 <settings>
 19     <!--使用jdbc的getGeneratedKeys獲取數據庫自增主鍵值-->
 20     <setting name="useGeneratedKeys" value="true"/>
 21     <!--使用列別名替換列名,默認true, eg:select name as title from table-->
 22     <setting name="useColumnLabel" value="true"/>
 23     <!--開啓駝峯命名轉換 table(create_time)-->entity(createTime)
 24     <setting name="mapUnderscoreToCamelCase" value="true"/>
 25 </settings>
 26 </configuration>
 27 
 28 (3)實現整合(spring-dao.xml操做)
 29 -->1.配置dataSource數據源
 30 jdbc.properties內容:
 31     jdbc.driver=com.mysql.jdbc.Driver
 32     jdbc.url=jdbc:mysql://localhost:3306/smbms?useUnicode=true&characterEncoding=UTF-8
 33     jdbc.username=root
 34     jdbc.password=123456
 35 
 36 <!--properties文件配置數據源-->
 37 <context:property-placeholder location="classpath:spring/jdbc.properties"/>
 38     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
 39         <!--配置鏈接池屬性-->
 40         <property name="driverClassName" value="${jdbc.driver}"/>
 41         <property name="url" value="${jdbc.url}"/>
 42         <property name="username" value="${jdbc.username}"/>
 43         <property name="password" value="${jdbc.password}"/>
 44     </bean>
 45 
 46 -->2.配置SqlSessionFactoryBean
 47    <!--SqlSession 包含了全部執行數據庫SQL語句的方法。可以直接地經過SqlSession實例執行映射SQL-->
 48     <!--缺乏sqlSessionFactory:No bean named 'sqlSessionFactory' available    完成對配置文件的讀取-->
 49     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 50         <!--注入數據庫鏈接池--><!--不然會出現java.lang.IllegalArgumentException: Property 'dataSource' is required-->
 51         <property name="dataSource" ref="dataSource"/>
 52 
 53         <!--掃描entity包,使用別名,設置這個之後再Mapper配置文件中在parameterType
 54         的值就不用寫成全路徑名了-->
 55         <property name="typeAliasesPackage" value="com.one.ssm.entity"/>
 56 
 57         <!--掃描mapper須要的xml文件-->
 58         <property name="mapperLocations" value="classpath:mapper/*.xml"/>
 59     </bean>
 60 
 61     <!-- 配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中 -->
 62     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 63         <!-- 注入sqlSessionFactory -->
 64         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
 65         <!-- 給出須要掃描Dao接口包 -->
 66         <property name="basePackage" value="com.one.ssm.dao"/>
 67     </bean>
 68 
 69 (4)注入映射器的兩種方式:使用了映射器注入就能夠不用寫dao層的接口的實現方法
 70 -->1.配置MapperFactoryBean生成映射器並注入到業務組件
 71  <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
 72         <!--mapperInterface屬性指定映射器,只能是某一個接口類型-->
 73         <property name="mapperInterface" value="com.one.ssm.dao.UserDao"/>
 74         <!-- 注入sqlSessionFactory -->
 75         <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
 76     </bean>
 77 
 78 -->2.配置MapperScannerConfiger生成映射器並注入到業務組件:優先使用MapperScannerConfiger,能夠批量生成映射器的實現
 79 <!--mybatis-spring提供了MapperScannerConfigurer,
 80     能夠掃描指定包中的接口並將它們直接註冊爲MapperFactoryBean,爲了簡化MapperFactoryBean映射器太多而致使多出的配置項-->
 81  <!--mybatis-spring提供了MapperScannerConfigurer,能夠掃描指定包中的接口並將它們直接註冊爲MapperFactoryBean-->
 82     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 83         <!-- 注入sqlSessionFactory -->
 84         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
 85         <!-- 給出須要掃描Dao接口包 -->
 86         <property name="basePackage" value="com.one.ssm.dao"/>
 87     </bean>
 88 
 89 (5)添加聲明式事務(spring-service.xml操做)
 90 -->使用xml配置方法配置聲明式事務
 91  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
 92     <property name="url" value="${jdbc.url}" />
 93     <property name="username" value="${jdbc.username}" />
 94     <property name="password" value="${jdbc.password}" />
 95     <property name="driverClassName" value="${jdbc.driver}" />
 96     </bean>
 97     <!--配置事務(事務就是對一系列的數據庫操做進行統一的提交或回滾操做)管理器-->
 98 <bean id="transactionManager"
 99           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
100         <property name="dataSource" ref="dataSource"/>
101     </bean>
102 -->使用註解添加聲明式事務
103  <!--聲明式事務註解的添加方式-->
104     <tx:annotation-driven transaction-manager="transactionManager"/>
spring整合mybatis

 springboot整合mybatis,須要在yml文件中添加相應的配置信息。數組

MyBatis的主要成員

Configuration:MyBatis全部的配置信息都保存在Configuration對象之中,配置文件中的大部分配置都會存儲到該類中。緩存

SqlSession:做爲MyBatis工做的主要頂層API,表示和數據庫交互時的會話,完成必要數據庫增刪改查功能。springboot

Executor:MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護。session

StatementHandler:封裝了JDBC Statement操做,負責對JDBC statement 的操做,如設置參數等。

ParameterHandler:負責對用戶傳遞的參數轉換成JDBC Statement所對應的數據類型。

ResultSetHandler:負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合。

TypeHandler:負責java數據類型和jdbc數據類型(也能夠說是數據表列類型)之間的映射和轉換,負責對statement對象設定特定的參數,對statement的返回結果result結果集取出特定的列

MappedStatement:MappedStatement維護一條<select|update|delete|insert>節點的封裝。

SqlSource:負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回。

BoundSql:表示動態生成的SQL語句以及相應的參數信息。

MyBatis的層次結構:

一、sqlSession -->excutor--> statementHander-->parameterHander--> typeHander-->(進入jdbc)statement(分爲:preparedStatement、simpleStatement、callableStatement)-->(取出結果)resultSet--> typeHander-->resultSetHandler-->statementHandler--->excutor-->sqlSession

MyBatis的初始化(解析配置文件和初始化Configuration的過程)

String resource = "mybatis.xml";
// 加載mybatis的配置文件(它也加載關聯的映射文件)
InputStream inputStream = null;
try {
    inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
    e.printStackTrace();
}

// 構建sqlSession的工廠
sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

首先會建立SqlSessionFactory建造者對象,而後由它進行建立SqlSessionFactory。這裏用到的是建造者模式,建造者模式最簡單的理解就是不手動new對象,而是由其餘類來進行對象的建立。

// SqlSessionFactoryBuilder類
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
//XMLConfigBuilder對象會進行XML配置文件的解析,實際爲configuration節點的解析操做。
        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.
        }
    }
}
 1 public Configuration parse() {
 2     if (parsed) {
 3         throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 4     }
 5     parsed = true;
 6     parseConfiguration(parser.evalNode("/configuration"));
 7     return configuration;
 8 }
 9 
10 private void parseConfiguration(XNode root) {
11     try {
12         //issue #117 read properties first
13         propertiesElement(root.evalNode("properties"));
14         Properties settings = settingsAsProperties(root.evalNode("settings"));
15         loadCustomVfs(settings);
16         typeAliasesElement(root.evalNode("typeAliases"));
17         pluginElement(root.evalNode("plugins"));
18         objectFactoryElement(root.evalNode("objectFactory"));
19         objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
20         reflectorFactoryElement(root.evalNode("reflectorFactory"));
21         settingsElement(settings);
22         // read it after objectFactory and objectWrapperFactory issue #631
23 
24         /* 處理environments節點數據 */
25         environmentsElement(root.evalNode("environments"));
26         databaseIdProviderElement(root.evalNode("databaseIdProvider"));
27         typeHandlerElement(root.evalNode("typeHandlers"));
28         mapperElement(root.evalNode("mappers"));
29     } catch (Exception e) {
30         throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
31     }
32 }
XMLConfigBuilder類

 在configuration節點下會依次解析properties/settings/.../mappers等節點配置。在解析environments節點時,會根據transactionManager的配置來建立事務管理器,根據dataSource的配置來建立DataSource對象,這裏麪包含了數據庫登陸的相關信息。在解析mappers節點時,會讀取該節點下全部的mapper文件,而後進行解析,並將解析後的結果存到configuration對象中。

 1 private void environmentsElement(XNode context) throws Exception {
 2     if (context != null) {
 3         if (environment == null) {
 4             environment = context.getStringAttribute("default");
 5         }
 6         for (XNode child : context.getChildren()) {
 7             String id = child.getStringAttribute("id");
 8             if (isSpecifiedEnvironment(id)) {
 9 
10                 /* 建立事務管理器 */
11                 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
12                 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
13                 DataSource dataSource = dsFactory.getDataSource();
14 
15                 /* 建造者模式 設計模式 */
16                 Environment.Builder environmentBuilder = new Environment.Builder(id)
17                         .transactionFactory(txFactory)
18                         .dataSource(dataSource);
19                 configuration.setEnvironment(environmentBuilder.build());
20             }
21         }
22     }
23 }
24 
25 // 解析單獨的mapper文件
26 private void mapperElement(XNode parent) throws Exception {
27     if (parent != null) {
28       for (XNode child : parent.getChildren()) {
29         if ("package".equals(child.getName())) {
30           String mapperPackage = child.getStringAttribute("name");
31           configuration.addMappers(mapperPackage);
32         } else {
33           String resource = child.getStringAttribute("resource");
34           String url = child.getStringAttribute("url");
35           String mapperClass = child.getStringAttribute("class");
36           if (resource != null && url == null && mapperClass == null) {
37             ErrorContext.instance().resource(resource);
38             InputStream inputStream = Resources.getResourceAsStream(resource);
39             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
40             mapperParser.parse(); // 開始解析mapper文件了 :)
41           } else if (resource == null && url != null && mapperClass == null) {
42             ErrorContext.instance().resource(url);
43             InputStream inputStream = Resources.getUrlAsStream(url);
44             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
45             mapperParser.parse();
46           } else if (resource == null && url == null && mapperClass != null) {
47             Class<?> mapperInterface = Resources.classForName(mapperClass);
48             configuration.addMapper(mapperInterface);
49           } else {
50             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
51           }
52         }
53       }
54     }
55   }
XMLConfigBuilder類

解析完MyBatis配置文件後,configuration就初始化完成了,而後根據configuration對象來建立SqlSession就初始化完成了

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

MyBatis的SQL查詢流程

經過封裝JDBC進行操做,而後使用Java反射技術完成JavaBean對象到數據庫參數之間的相互轉換,這種映射關係就是有TypeHandler對象來完成的,在獲取數據表對應的元數據時,會保存該表全部列的數據庫類型。

sqlSession = sessionFactory.openSession();

User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1);
System.out.println(user);

調用selectOne方法進行SQL查詢,selectOne方法最後調用的是selectList,在selectList中,會查詢configuration中存儲的MappedStatement對象,mapper文件中一個sql語句的配置對應一個MappedStatement對象,而後調用執行器進行查詢操做。

public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    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;
    }
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        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();
    }
}

 執行器在query操做中,優先會查詢緩存是否命中,命中則直接返回,不然從數據庫中查詢。

 1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
 2     /* 獲取關聯參數的sql,boundSql */
 3     BoundSql boundSql = ms.getBoundSql(parameterObject);
 4     /* 建立cache key值 */
 5     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
 6     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
 7 }
 8 
 9 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
10       throws SQLException {
11     /* 獲取二級緩存實例 */
12     Cache cache = ms.getCache();
13     if (cache != null) {
14         flushCacheIfRequired(ms);
15         if (ms.isUseCache() && resultHandler == null) {
16             ensureNoOutParams(ms, parameterObject, boundSql);
17             @SuppressWarnings("unchecked")
18             List<E> list = (List<E>) tcm.getObject(cache, key);
19             if (list == null) {
20                 list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
21                 tcm.putObject(cache, key, list); // issue #578 and #116
22             }
23             return list;
24         }
25     }
26     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
27 }
28 
29 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
30     List<E> list;
31     /**
32      * 先往localCache中插入一個佔位對象,這個地方
33      */
34     localCache.putObject(key, EXECUTION_PLACEHOLDER);
35     try {
36         list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
37     } finally {
38         localCache.removeObject(key);
39     }
40 
41     /* 往緩存中寫入數據,也就是緩存查詢結果 */
42     localCache.putObject(key, list);
43     if (ms.getStatementType() == StatementType.CALLABLE) {
44         localOutputParameterCache.putObject(key, parameter);
45     }
46     return list;
CachingExecutor類

真正的doQuery操做是由SimplyExecutor代理來完成的,該方法中有2個子流程,一個是SQL參數的設置,另外一個是SQL查詢操做和結果集的封裝。

 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 
 7         /* 子流程1: SQL查詢參數的設置 */
 8         stmt = prepareStatement(handler, ms.getStatementLog());
 9 
10         /* 子流程2: SQL查詢操做和結果集封裝 */
11         return handler.<E>query(stmt, resultHandler);
12     } finally {
13         closeStatement(stmt);
14     }
15 }
子流程

子流程1 SQL查詢參數的設置:

 1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
 2     Statement stmt;
 3     /* 獲取Connection鏈接 */
 4     Connection connection = getConnection(statementLog);
 5 
 6     /* 準備Statement */
 7     stmt = handler.prepare(connection, transaction.getTimeout());
 8 
 9     /* 設置SQL查詢中的參數值 */
10     handler.parameterize(stmt);
11     return stmt;
12 }
13 
14 // DefaultParameterHandler類
15 public void setParameters(PreparedStatement ps) {
16     /**
17      * 設置SQL參數值,從ParameterMapping中讀取參數值和類型,而後設置到SQL語句中
18      */
19     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
20     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
21     if (parameterMappings != null) {
22         for (int i = 0; i < parameterMappings.size(); i++) {
23             ParameterMapping parameterMapping = parameterMappings.get(i);
24             if (parameterMapping.getMode() != ParameterMode.OUT) {
25                 Object value;
26                 String propertyName = parameterMapping.getProperty();
27                 if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
28                     value = boundSql.getAdditionalParameter(propertyName);
29                 } else if (parameterObject == null) {
30                     value = null;
31                 } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
32                     value = parameterObject;
33                 } else {
34                     MetaObject metaObject = configuration.newMetaObject(parameterObject);
35                     value = metaObject.getValue(propertyName);
36                 }
37                 TypeHandler typeHandler = parameterMapping.getTypeHandler();
38                 JdbcType jdbcType = parameterMapping.getJdbcType();
39                 if (value == null && jdbcType == null) {
40                     jdbcType = configuration.getJdbcTypeForNull();
41                 }
42                 try {
43                     typeHandler.setParameter(ps, i + 1, value, jdbcType);
44                 } catch (TypeException e) {
45                     throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
46                 } catch (SQLException e) {
47                     throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
48                 }
49             }
50         }
51     }
52 }
子流程1 SQL查詢參數的設置:

子流程2 SQL查詢結果集的封裝:

 1 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
 2     PreparedStatement ps = (PreparedStatement) statement;
 3     // 執行查詢操做
 4     ps.execute();
 5     // 執行結果集封裝
 6     return resultSetHandler.<E> handleResultSets(ps);
 7 }
 8 
 9 // DefaultReseltSetHandler類
10 public List<Object> handleResultSets(Statement stmt) throws SQLException {
11     ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
12 
13     final List<Object> multipleResults = new ArrayList<Object>();
14 
15     int resultSetCount = 0;
16     /**
17      * 獲取第一個ResultSet,同時獲取數據庫的MetaData數據,包括數據表列名、列的類型、類序號等。
18      * 這些信息都存儲在了ResultSetWrapper中了
19      */
20     ResultSetWrapper rsw = getFirstResultSet(stmt);
21 
22     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
23     int resultMapCount = resultMaps.size();
24     validateResultMapsCount(rsw, resultMapCount);
25     while (rsw != null && resultMapCount > resultSetCount) {
26       ResultMap resultMap = resultMaps.get(resultSetCount);
27       handleResultSet(rsw, resultMap, multipleResults, null);
28       rsw = getNextResultSet(stmt);
29       cleanUpAfterHandlingResultSet();
30       resultSetCount++;
31     }
32 
33     String[] resultSets = mappedStatement.getResultSets();
34     if (resultSets != null) {
35       while (rsw != null && resultSetCount < resultSets.length) {
36         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
37         if (parentMapping != null) {
38           String nestedResultMapId = parentMapping.getNestedResultMapId();
39           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
40           handleResultSet(rsw, resultMap, null, parentMapping);
41         }
42         rsw = getNextResultSet(stmt);
43         cleanUpAfterHandlingResultSet();
44         resultSetCount++;
45       }
46     }
47 
48     return collapseSingleResultList(multipleResults);
49   }
SQL查詢結果集的封裝

ResultSetWrapperResultSet的包裝類,調用getFirstResultSet方法獲取第一個ResultSet,同時獲取數據庫的MetaData數據,包括數據表列名、列的類型、類序號等,這些信息都存儲在ResultSetWrapper類中了。而後調用handleResultSet方法來來進行結果集的封裝。

 1 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
 2     try {
 3         if (parentMapping != null) {
 4             handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
 5         } else {
 6             if (resultHandler == null) {
 7                 DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
 8                 handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
 9                 multipleResults.add(defaultResultHandler.getResultList());
10             } else {
11                 handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
12             }
13         }
14     } finally {
15         // issue #228 (close resultsets)
16         closeResultSet(rsw.getResultSet());
17     }
18 }
DefaultResultSetHandler類

調用handleRowValues方法進行結果值的設置

 1 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
 2     if (resultMap.hasNestedResultMaps()) {
 3         ensureNoRowBounds();
 4         checkResultHandler();
 5         handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
 6     } else {
 7         // 封裝數據
 8         handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
 9     }
10 }
11 
12 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
13         throws SQLException {
14     DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
15     skipRows(rsw.getResultSet(), rowBounds);
16     while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
17         ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
18         Object rowValue = getRowValue(rsw, discriminatedResultMap);
19         storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
20     }
21 }
22 
23 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
24     final ResultLoaderMap lazyLoader = new ResultLoaderMap();
25     // createResultObject爲新建立的對象,數據表對應的類
26     Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
27     if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
28         final MetaObject metaObject = configuration.newMetaObject(rowValue);
29         boolean foundValues = this.useConstructorMappings;
30         if (shouldApplyAutomaticMappings(resultMap, false)) {
31             // 這裏把數據填充進去,metaObject中包含了resultObject信息
32             foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
33         }
34         foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
35         foundValues = lazyLoader.size() > 0 || foundValues;
36         rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
37     }
38     return rowValue;
39 }
40 
41 private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
42     List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
43     boolean foundValues = false;
44     if (autoMapping.size() > 0) {
45         // 這裏進行for循環調用,由於user表中總共有7列,因此也就調用7次
46         for (UnMappedColumnAutoMapping mapping : autoMapping) {
47             // 這裏將esultSet中查詢結果轉換爲對應的實際類型
48             final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
49             if (value != null) {
50                 foundValues = true;
51             }
52             if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
53                 // gcode issue #377, call setter on nulls (value is not 'found')
54                 metaObject.setValue(mapping.property, value);
55             }
56         }
57     }
58     return foundValues;
59 }
DefaultResultSetHandler類

mapping.typeHandler.getResult會獲取查詢結果值的實際類型,好比咱們user表中id字段爲int類型,那麼它就對應Java中的Integer類型,而後經過調用statement.getInt("id")來獲取其int值,其類型爲IntegermetaObject.setValue方法會把獲取到的Integer值設置到Java類中的對應字段。

metaValue.setValue方法最後會調用到Java類中對應數據域的set方法,這樣也就完成了SQL查詢結果集的Java類封裝過程。

 1 public void setValue(String name, Object value) {
 2     PropertyTokenizer prop = new PropertyTokenizer(name);
 3     if (prop.hasNext()) {
 4         MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
 5         if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
 6             if (value == null && prop.getChildren() != null) {
 7                 // don't instantiate child path if value is null
 8                 return;
 9             } else {
10                 metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
11             }
12         }
13         metaValue.setValue(prop.getChildren(), value);
14     } else {
15         objectWrapper.set(prop, value);
16     }
17 }
MetaObject類

MyBatis緩存

MyBatis提供了一級緩存和二級緩存:

一級緩存是SqlSession級別的緩存,每一個SqlSession對象都有一個哈希表用於緩存數據,不一樣SqlSession對象之間緩存不共享。同一個SqlSession對象對象執行2遍相同的SQL查詢,在第一次查詢執行完畢後將結果緩存起來,這樣第二遍查詢就不用向數據庫查詢了,直接返回緩存結果便可。MyBatis默認是開啓一級緩存的。

二級緩存是mapper級別的緩存,二級緩存是跨SqlSession的,多個SqlSession對象能夠共享同一個二級緩存。不一樣的SqlSession對象執行兩次相同的SQL語句,第一次會將查詢結果進行緩存,第二次查詢直接返回二級緩存中的結果便可。MyBatis默認是不開啓二級緩存的,能夠在配置文件中使用以下配置來開啓二級緩存:

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

當SQL語句進行更新操做(刪除/添加/更新)時,會清空對應的緩存,保證緩存中存儲的都是最新的數據。

 

MyBatis動態sql

(1)多條件查詢
<select id="getUserList" resultMap="userList">
        SELECT u.*,r.roleName FROM smbms_user u,smbms_role r 
        WHERE u.userName LIKE CONCAT('%','#{userName}','%')
        AND u.userRole=#{userRole} 
        AND u.userRole=r.id
    </select>

(2)if-where的用法
<select id="getUserList" resultType="User">
    SELECT * FROM smbms_user
    <where>
        <if test="userName!=null AND userName!=">
            AND userName LIKE CONCAT('%','#{userName}','%')
        </if>
        <if test="userRole!=null">
            AND userRole=#{userRole}
        </if>
    </where>
</select>
 <where>會自動去掉第一個and。

(3)if-trim
<select id="getUserList" resultType="User">
         SELECT * FROM smbms_user
         <trim prefix="where" prefixOverrides="and|or">
             <if test="userName!=null and userName!=">
                 AND userName LIKE CONCAT('%','#{userName}','%')
             </if>
             <if test="userRole!=null">
                 AND userRole=#{userRole}
             </if>
         </trim>
    </select>
<trim prefix="where" prefixOverrides="and|or">做用爲自動添加where或者對and|or的自動忽略

(4)if-set 動態更新,假設沒有涉及到的或者不須要更新的就能夠不用更新,set標籤能夠自動剔除逗號(,)
<!--parameterType:屬性名,若是是select就寫resultMap,是其餘的寫對應實體對應的路徑位置-->
    <update id="modifyXxx" parameterType="User">
        UPDATE smbms_user
        <set>
            <if test="userCode!=NULL">
                userCode=#{userCode},
            </if>
            <if test="userName!=null">
                userName=#{userName},
            </if>
            <if test="phone!=null">
                phone=#{phone},
            </if>
        </set>
        WHERE id=#{id}
    </update>

(5)if-set中的trim
<update id="modify" parameterType="User">
        UPDATE smbms_user
        <trim prefix="set" prefixOverrides="," suffix="where id=#{id}">
        </trim>
            <if test="userCode!=null">
                userCode=#{userCode},
            </if>
            <if test="userName!=null">
                userName=#{userName},
            </if>
            <if test="phone!=null">
                phone=#{phone},
            </if>
    </update>
<trim suffix="where id=#{id}">在trim內容的後面加上後綴

(6)foreach迭代collection數組類型的入參:對於sql語句中含有in語句,則須要foreach標籤來實現sql條件的迭代
         eg:SELECT u.*   from smbms_user u WHERE userRole in(2,4)
<select id="getUserByRoleId_foreach_array" resultMap="userMapByRole">
        SELECT * FROM smbms_user WHERE userRole IN 
        <foreach collection="array" item="roleIds" open="(" separator="," close=")">
            #{roleIds}
        </foreach>
    </select>
    <resultMap id="userMapByRole" type="User">
        <id property="id" column="id"/>
        <result property="userCode" column="userCode"/>
        <result property="userName" column="userName"/>
    </resultMap>
-->Dao層接口方法爲:LIst<User> getUserByRoleId_foreach_array(Integer[] roleIds)
-->item :集合中進行迭代時的別名,
-->index :指定一個名稱,表示在迭代過程當中每次迭代到的位置
-->separator:每次進行迭代用什麼分隔符號,in條件語句用逗號(,)爲分隔符
-->open:表示該語句以什麼開始的,in語句以 「(」開始
-->close:表示該語句以什麼符號結束 ,in語句以「)」結束
-->collection:若是是入參類型是參數是LIst,則collection屬性值爲list;是一個數組,則爲array,若是爲多參數,則須要封裝成一個Map進行處理

(7)foreach迭代list類型的入參
-->Dao層接口方法爲:LIst<User> getUserByRoleId_foreach_list(List<Integer> roleList);
<select id="getUserByRoleId_foreach_list" resultMap="userMapByRole">
        SELECT * FROM smbms_user WHERE userRole IN 
        <foreach collection="list" item="roleIds" open="(" separator="," close=")">
            #{roleIds}
        </foreach>
    </select>
    <resultMap id="userMapByRole" type="User">
        <id property="id" column="id"/>
        <result property="userCode" column="userCode"/>
        <result property="userName" column="userName"/>
    </resultMap>

(8)foreach迭代Map類型的入參
接口方法:public List<User> getUserByRoleId_foreach_map(Map<String,Object>  conditionMap);
@Test
    public void getUserListByUserName() throws Exception {
        Map<String,Object> conditionMap=new HashMap<String, Object>();
        List<Integer> roleList=new ArrayList<Integer>();
        roleList.add(2);
        //gender是一個限定條件
        conditionMap.put("gender",1);
        //roleIds 對應collection
        conditionMap.put("roleIds",roleList);
        System.out.println("----------------------------------");
        System.out.println(userDao.getUserByRoleId_foreach_map(conditionMap));
        System.out.println("-------------------------------------");

 <select id="getUserByRoleId_foreach_map" resultMap="userMapByRole">
        SELECT * FROM smbms_user WHERE gender=#{gender} and userRole in
        <foreach collection="roleIds" item="m" open="(" separator="," close=")">
            #{m}
        </foreach>
    </select>

(9)choose(when-otherwise)
接口方法:public List<User> getUserList_choose(@Param("userName") String userName, @Param("userRole")Integer userRole,
                                     @Param("userCode")String userCode, @Param("creationDate")Date creationDate);
測試類:
@Test
    public void getUserList_choose() throws Exception {
        List<User> userList =new ArrayList<>();
        String userName="張明";
        Integer userRole=2;
        String userCode="";
        Date creationDate=new SimpleDateFormat("yyy-MM-dd").parse("2030-10-26");
        userList=userDao.getUserList_choose(userName,userRole,userCode,creationDate);
        System.out.println(userList);
mapper:
<select id="getUserList_choose" resultMap="userMapByRole">
        SELECT * FROM smbms_user WHERE 1=1
        <choose>
            <when test="userName!=null and userName!=''">
                AND userName LIKE CONCAT('%',#{userName},'%')
            </when>
            <when test="userCode!=null and userCode!=''">
                AND userCode LIKE CONCAT('%',#{userCode},'%')
            </when>
            <when test="userRole!=null and userRole!=''">
                AND userRole=#{userRole}
            </when>
            <otherwise>
                AND YEAR(creationDate)=YEAR(#{creationDate})
            </otherwise>
        </choose>
    </select>
-->when:當知足一個條件時跳出循環,
-->otherwise:當全部的when都不知足的時候,執行otherwise
-->choose:至關於switch
-->where 1=1:能夠不須要處理多餘的and
相關文章
相關標籤/搜索