MyBatis框架及原理分析
MyBatis 是支持定製化 SQL、存儲過程以及高級映射的優秀的持久層框架,其主要就完成2件事情:
1.封裝JDBC操作
2.利用反射打通Java類與SQL語句之間的相互轉換
MyBatis的主要設計目的就是讓我們對執行SQL語句時對輸入輸出的數據管理更加方便,所以方便地寫出SQL和方便地獲取SQL的執行結果纔是MyBatis的核心競爭力。
MyBatis的配置
MyBatis框架和其他絕大部分框架一樣,需要一個配置文件,其配置文件大致如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="false"/>
<!--<setting name="logImpl" value="STDOUT_LOGGING"/> <!– 打印日誌信息 –>-->
</settings>
<typeAliases>
<typeAlias type="com.luo.dao.UserDao" alias="User"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/> <!--事務管理類型-->
<dataSource type="POOLED">
<property name="username" value="luoxn28"/>
<property name="password" value="123456"/>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.1.150/ssh_study"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="userMapper.xml"/>
</mappers>
</configuration>
以上配置中,最重要的是數據庫參數的配置,比如用戶名密碼等,如果配置了數據表對應的mapper文件,則需要將其加入到節點下。
MyBatis的主要成員
- Configuration MyBatis所有的配置信息都保存在Configuration對象之中,配置文件中的大部分配置都會存儲到該類中
- SqlSession 作爲MyBatis工作的主要頂層API,表示和數據庫交互時的會話,完成必要數據庫增刪改查功能
- Executor MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護
- StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數等
- ParameterHandler 負責對用戶傳遞的參數轉換成JDBC Statement 所對應的數據類型
- ResultSetHandler 負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合
- TypeHandler 負責java數據類型和jdbc數據類型(也可以說是數據表列類型)之間的映射和轉換
- MappedStatement MappedStatement維護一條<select|update|delete|insert>節點的封裝
- SqlSource 負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回
- BoundSql 表示動態生成的SQL語句以及相應的參數信息
以上主要成員在一次數據庫操作中基本都會涉及,在SQL操作中重點需要關注的是SQL參數什麼時候被設置和結果集怎麼轉換爲JavaBean對象的,這兩個過程正好對應StatementHandler和ResultSetHandler類中的處理邏輯。
MyBatis的初始化
MyBatis的初始化的過程其實就是解析配置文件和初始化Configuration的過程,MyBatis的初始化過程可用以下幾行代碼來表述:
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 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.
}
}
}
XMLConfigBuilder對象會進行XML配置文件的解析,實際爲configuration節點的解析操作。
// XMLConfigBuilder類
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
/* 處理environments節點數據 */
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);
}
}
在configuration節點下會依次解析properties/settings/…/mappers等節點配置。在解析environments節點時,會根據transactionManager的配置來創建事務管理器,根據dataSource的配置來創建DataSource對象,這裏麪包含了數據庫登錄的相關信息。在解析mappers節點時,會讀取該節點下所有的mapper文件,然後進行解析,並將解析後的結果存到configuration對象中。
// XMLConfigBuilder類
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
/* 創建事務管理器 */
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
/* 建造者模式 設計模式 */
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
// 解析單獨的mapper文件
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse(); // 開始解析mapper文件了 :)
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
解析完MyBatis配置文件後,configuration就初始化完成了,然後根據configuration對象來創建SqlSession,到這裏時,MyBatis的初始化的征程已經走完了。
// SqlSessionFactoryBuilder類
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
MyBatis的SQL查詢流程
SQL語句的執行纔是MyBatis的重要職責,該過程就是通過封裝JDBC進行操作,然後使用Java反射技術完成JavaBean對象到數據庫參數之間的相互轉換,這種映射關係就是有TypeHandler對象來完成的,在獲取數據表對應的元數據時,會保存該表所有列的數據庫類型,大致邏輯如下所示:
/* Get resultSet metadata */
ResultSetMetaData metaData = resultSet.getMetaData();
int column = metaData.getColumnCount();
for (int i = 1; i <= column; i++) {
JdbcType jdbcType = JdbcType.forCode(metaData.getColumnType(i));
typeHandlers.add(TypeHandlerRegistry.getTypeHandler(jdbcType));
columnNames.add(metaData.getColumnName(i));
}
使用如下代碼進行SQL查詢操作:
sqlSession = sessionFactory.openSession();
User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1);
System.out.println(user);
創建sqlSession的過程其實就是根據configuration中的配置來創建對應的類,然後返回創建的sqlSession對象。調用selectOne方法進行SQL查詢,selectOne方法最後調用的是selectList,在selectList中,會查詢configuration中存儲的MappedStatement對象,mapper文件中一個sql語句的配置對應一個MappedStatement對象,然後調用執行器進行查詢操作。
// DefaultSqlSession類
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操作中,優先會查詢緩存是否命中,命中則直接返回,否則從數據庫中查詢。
// CachingExecutor類
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
/* 獲取關聯參數的sql,boundSql */
BoundSql boundSql = ms.getBoundSql(parameterObject);
/* 創建cache key值 */
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
/* 獲取二級緩存實例 */
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
/**
* 先往localCache中插入一個佔位對象,這個地方
*/
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
/* 往緩存中寫入數據,也就是緩存查詢結果 */
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
真正的doQuery操作是由SimplyExecutor代理來完成的,該方法中有2個子流程,一個是SQL參數的設置,另一個是SQL查詢操作和結果集的封裝。
// 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);
/* 子流程1: SQL查詢參數的設置 */
stmt = prepareStatement(handler, ms.getStatementLog());
/* 子流程2: SQL查詢操作和結果集封裝 */
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
子流程1 SQL查詢參數的設置:
首先獲取數據庫connection連接,然後準備statement,然後就設置SQL查詢中的參數值。打開一個connection連接,在使用完後不會close,而是存儲下來,當下次需要打開連接時就直接返回。
// SimpleExecutor類
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
/* 獲取Connection連接 */
Connection connection = getConnection(statementLog);
/* 準備Statement */
stmt = handler.prepare(connection, transaction.getTimeout());
/* 設置SQL查詢中的參數值 */
handler.parameterize(stmt);
return stmt;
}
// DefaultParameterHandler類
public void setParameters(PreparedStatement ps) {
/**
* 設置SQL參數值,從ParameterMapping中讀取參數值和類型,然後設置到SQL語句中
*/
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
子流程2 SQL查詢結果集的封裝:
// SimpleExecutor類
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 執行查詢操作
ps.execute();
// 執行結果集封裝
return resultSetHandler.<E> handleResultSets(ps);
}
// DefaultReseltSetHandler類
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
/**
* 獲取第一個ResultSet,同時獲取數據庫的MetaData數據,包括數據表列名、列的類型、類序號等。
* 這些信息都存儲在了ResultSetWrapper中了
*/
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
ResultSetWrapper是ResultSet的包裝類,調用getFirstResultSet方法獲取第一個ResultSet,同時獲取數據庫的MetaData數據,包括數據表列名、列的類型、類序號等,這些信息都存儲在ResultSetWrapper類中了。然後調用handleResultSet方法來來進行結果集的封裝。
// DefaultResultSetHandler類
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
這裏調用handleRowValues方法進行結果值的設置。
// DefaultResultSetHandler類
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 封裝數據
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
skipRows(rsw.getResultSet(), rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// createResultObject爲新創建的對象,數據表對應的類
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 這裏把數據填充進去,metaObject中包含了resultObject信息
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
}
return rowValue;
}
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (autoMapping.size() > 0) {
// 這裏進行for循環調用,因爲user表中總共有7列,所以也就調用7次
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 這裏將esultSet中查詢結果轉換爲對應的實際類型
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
mapping.typeHandler.getResult會獲取查詢結果值的實際類型,比如我們user表中id字段爲int類型,那麼它就對應Java中的Integer類型,然後通過調用statement.getInt(“id”)來獲取其int值,其類型爲Integer。metaObject.setValue方法會把獲取到的Integer值設置到Java類中的對應字段。
// MetaObject類
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null && prop.getChildren() != null) {
// don't instantiate child path if value is null
return;
} else {
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
}
metaValue.setValue(prop.getChildren(), value);
} else {
objectWrapper.set(prop, value);
}
}
metaValue.setValue方法最後會調用到Java類中對應數據域的set方法,這樣也就完成了SQL查詢結果集的Java類封裝過程。最後貼一張調用棧到達Java類的set方法中的快照:
MyBatis緩存
MyBatis提供查詢緩存,用於減輕數據庫壓力,提高性能。MyBatis提供了一級緩存和二級緩存。
一級緩存是SqlSession級別的緩存,每個SqlSession對象都有一個哈希表用於緩存數據,不同SqlSession對象之間緩存不共享。同一個SqlSession對象對象執行2遍相同的SQL查詢,在第一次查詢執行完畢後將結果緩存起來,這樣第二遍查詢就不用向數據庫查詢了,直接返回緩存結果即可。MyBatis默認是開啓一級緩存的。
二級緩存是mapper級別的緩存,二級緩存是跨SqlSession的,多個SqlSession對象可以共享同一個二級緩存。不同的SqlSession對象執行兩次相同的SQL語句,第一次會將查詢結果進行緩存,第二次查詢直接返回二級緩存中的結果即可。MyBatis默認是不開啓二級緩存的,可以在配置文件中使用如下配置來開啓二級緩存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
當SQL語句進行更新操作(刪除/添加/更新)時,會清空對應的緩存,保證緩存中存儲的都是最新的數據。MyBatis的二級緩存對細粒度的數據級別的緩存實現不友好,比如如下需求:對商品信息進行緩存,由於商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用mybatis的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因爲mybaits的二級緩存區域以mapper爲單位劃分,當一個商品信息變化會將所有商品信息的緩存數據全部清空。解決此類問題需要在業務層根據需求對數據有針對性緩存,具體業務具體實現。