如下我的學習筆記,僅供參考,歡迎指正。java
MyBatis 是支持定製化 SQL、存儲過程以及高級映射的持久層框架,其主要就完成2件事情:mysql
封裝JDBC操做spring
利用反射打通Java類與SQL語句之間的相互轉換sql
MyBatis的主要設計目的就是讓咱們對執行SQL語句時對輸入輸出的數據管理更加方便,因此方便地寫出SQL和方便地獲取SQL的執行結果纔是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"/>
springboot整合mybatis,須要在yml文件中添加相應的配置信息。數組
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語句以及相應的參數信息。
一、sqlSession -->excutor--> statementHander-->parameterHander--> typeHander-->(進入jdbc)statement(分爲:preparedStatement、simpleStatement、callableStatement)-->(取出結果)resultSet--> typeHander-->resultSetHandler-->statementHandler--->excutor-->sqlSession
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 }
在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 }
解析完MyBatis
配置文件後,configuration
就初始化完成了,而後根據configuration
對象來建立SqlSession就初始化完成了
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
經過封裝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;
真正的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 }
子流程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 }
ResultSetWrapper
是ResultSet
的包裝類,調用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 }
調用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 }
mapping.typeHandler.getResult
會獲取查詢結果值的實際類型,好比咱們user表中id字段爲int類型,那麼它就對應Java中的Integer類型,而後經過調用statement.getInt("id")
來獲取其int值,其類型爲Integer
。metaObject.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 }
MyBatis提供了一級緩存和二級緩存:
一級緩存是SqlSession
級別的緩存,每一個SqlSession
對象都有一個哈希表用於緩存數據,不一樣SqlSession
對象之間緩存不共享。同一個SqlSession
對象對象執行2遍相同的SQL查詢,在第一次查詢執行完畢後將結果緩存起來,這樣第二遍查詢就不用向數據庫查詢了,直接返回緩存結果便可。MyBatis默認是開啓一級緩存的。
二級緩存是mapper
級別的緩存,二級緩存是跨SqlSession
的,多個SqlSession
對象能夠共享同一個二級緩存。不一樣的SqlSession
對象執行兩次相同的SQL語句,第一次會將查詢結果進行緩存,第二次查詢直接返回二級緩存中的結果便可。MyBatis默認是不開啓二級緩存的,能夠在配置文件中使用以下配置來開啓二級緩存:
<settings> <setting name="cacheEnabled" value="true"/> </settings>
當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