縱覽全網幾乎沒有人對localOutputParameterCache做過解釋可免,嘿嘿逞泄,那我嘗試來解釋下斑鸦。
首先勺择,既然你想知道localOutputParameterCache的作用,我就當你知道localCache(MyBatis的一級緩存)涩嚣。然后崇众,我告訴你localOutputParameterCache也是一級緩存,只不過它作用的不是我們理論上的返回結果航厚,而是我們請求的參數(可以是DO)顷歌,更進一步說他是對存儲過程的一種緩存,下面我為大家11介紹幔睬。
1眯漩、localOutputParameterCache的創(chuàng)建時機
// class BaseExecutor
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
// 創(chuàng)建時機在Executor創(chuàng)建的時候,和localCache一起被創(chuàng)建
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
2、設置Object parameter(請求參數)值
// class DefaultResultSetHandler
@Override
public void handleOutputParameters(CallableStatement cs) throws SQLException {
final Object parameterObject = parameterHandler.getParameterObject();
final MetaObject metaParam = configuration.newMetaObject(parameterObject);
final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
for (int i = 0; i < parameterMappings.size(); i++) {
final ParameterMapping parameterMapping = parameterMappings.get(i);
// 只有mode值為:OUT或INOUT時赦抖,才進行存儲過程返回結果的設置
if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
if (ResultSet.class.equals(parameterMapping.getJavaType())) {
handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam);
} else {
final TypeHandler<?> typeHandler = parameterMapping.getTypeHandler();
// 從存儲過程中獲取到的值舱卡,設置到請求參數的映射中
metaParam.setValue(parameterMapping.getProperty(), typeHandler.getResult(cs, i + 1));
}
}
}
}
這樣設置的結果:是將存儲過程中的返回結果設置到Object parameter中,可以打斷點進行查看驗證队萤。
3轮锥、使用的時機-設置值
// class BaseExecutor
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
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) {
// 注意:只有在statementType="CALLABLE"的時候,設置值
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
這樣設置的結果:是將封裝好的Object parameter的結果設置到localOutputParameterCache中要尔。
4舍杜、使用的時機-獲取設置的值
// class BaseExecutor
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
// 從localOutputParameterCache獲取緩存的值
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
// 將緩存中的值設置到Object parameter請求參數中
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
這樣做的結果:就是從localOutputParameterCache中查存儲過程的結果,而不是去調數據庫赵辕。
總結:localOutputParameterCache也是一級緩存既绩,只不過是用來緩存存儲過程的返回結果。
補充:分享一個不完整的小demo(我理解的看到這最起碼你的應用也有了)还惠,大家可以進行調試時使用:
1熬词、表SQL
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tbl_employee
-- ----------------------------
DROP TABLE IF EXISTS `tbl_employee`;
CREATE TABLE `tbl_employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`last_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`gender` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`gmt_create` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
2、簡單的存儲過程
CREATE DEFINER=`root`@`localhost` PROCEDURE `get_employee_by_procedure`(
IN inputId INT,
OUT lastName VARCHAR(256),
OUT gender CHAR,
OUT email VARCHAR(256),
OUT get_create DATETIME
)
BEGIN
SELECT
e.last_name, e.gender, e.email, e.gmt_create
INTO lastName, gender, email, get_create
FROM tbl_employee e WHERE e.id = inputId;
END
簡單說明吸重,在Navicat中對應數據庫下的函數下進行保存即可:
3互拾、編寫Mapper.xml的映射
<!--
useCache="false"是避免二級緩存對其影響,查看源碼自行了解嚎幸;
statementType="CALLABLE"是開啟使用該localOutputParameterCache緩存颜矿,具體含義查看官方文檔即可
-->
<select id="getEmpByProcedure" useCache="false" statementType="CALLABLE">
{CALL get_employee_by_procedure(
#{id, mode=IN, jdbcType=INTEGER},
#{lastName, mode=OUT, jdbcType=VARCHAR, javaType=java.lang.String},
#{email, mode=OUT, jdbcType=CHAR, javaType=java.lang.String},
#{gender, mode=OUT, jdbcType=VARCHAR, javaType=java.lang.String},
#{gmtCreate, mode=OUT, jdbcType=DATE, javaType=java.util.Date}
)}
</select>
4、編寫Mapper接口方法
public interface EmployeeMapper {
/**
* 通過存儲過程查詢員工信息(無返回結果)
* @param employee 員工對象
*/
void getEmpByProcedure(Employee employee);
}
注意:存儲過程的返回結果會封裝在請求參數中嫉晶,所以請求參數中要包含返回的參數骑疆。
5、編寫DO
@Data
public class Employee implements Serializable {
private static final long serialVersionUID = -6843908059513315549L;
private Integer id;
private String lastName;
/** 0:女替废;1:男 */
private String gender;
private String email;
private Date gmtCreate;
}
6箍铭、編寫測試方法
@Slf4j
public class ProcedureTest {
private SqlSessionFactory getSqlSessionFactory() throws IOException {
try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
return new SqlSessionFactoryBuilder().build(inputStream);
}
}
@Test
public void procedureTest() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2椎镣、獲取sqlSession對象
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
// 3诈火、獲取接口實現類對象
EmployeeMapper mapper1 = sqlSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = sqlSession.getMapper(EmployeeMapper.class);
// mapper1查詢
Employee employee1 = new Employee();
employee1.setId(1);
mapper1.getEmpByProcedure(employee1);
// mapper2查詢
Employee employee2 = new Employee();
employee2.setId(1);
mapper2.getEmpByProcedure(employee2);
System.out.println(employee1);
System.out.println(employee2);
// mapper1和mapper2的查詢結果中引用對象比較:
// 比較地址值是否相等,從而說明是否使用了一級緩存
System.out.println(employee1.getGmtCreate() == employee2.getGmtCreate());
} catch (Exception e) {
log.error("error: {}", e, e.getMessage());
}
}
}
說明:大家可以通過使用關閉一級緩存的方法將一級緩存關閉后状答,查看運行結果冷守,從而進一步論證localOutputParameterCache就是“一級緩存”。
你會發(fā)現:
在不關閉一級緩存的情況下惊科,對象中的引用屬性的值比較是:true
而在關閉一級緩存的情況下拍摇,對象中的引用屬性的值比較是:false
關閉一級緩存的方式可以是:
<select id="getEmpByProcedure" useCache="false" statementType="CALLABLE" flushCache="true">
{CALL get_employee_by_procedure(
#{id, mode=IN, jdbcType=INTEGER},
#{lastName, mode=OUT, jdbcType=VARCHAR, javaType=java.lang.String},
#{email, mode=OUT, jdbcType=CHAR, javaType=java.lang.String},
#{gender, mode=OUT, jdbcType=VARCHAR, javaType=java.lang.String},
#{gmtCreate, mode=OUT, jdbcType=DATE, javaType=java.util.Date}
)}
</select>
沒錯,就是在標簽中添加 flushCache="true"即可馆截。
希望這篇文章能解決你的困惑充活,幫忙點個贊不過分吧。