纵览全网几乎没有人对localOutputParameterCache做过解释,嘿嘿,那我尝试来解释下。
首先,既然你想知道localOutputParameterCache的作用,我就当你知道localCache(MyBatis的一级缓存)。然后,我告诉你localOutputParameterCache也是一级缓存,只不过它作用的不是我们理论上的返回结果,而是我们请求的参数(可以是DO),更进一步说他是对存储过程的一种缓存,下面我为大家11介绍。
1、localOutputParameterCache的创建时机
// class BaseExecutor
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
// 创建时机在Executor创建的时候,和localCache一起被创建
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 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 List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List 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的映射
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就是“一级缓存”。
你会发现:
在不关闭一级缓存的情况下,对象中的引用属性的值比较是:true
而在关闭一级缓存的情况下,对象中的引用属性的值比较是:false
关闭一级缓存的方式可以是:
没错,就是在标签中添加 flushCache="true"即可。
希望这篇文章能解决你的困惑,帮忙点个赞不过分吧。