{"record":{"id":"baaaee2383550036","repo":"mybatis/mybatis-3","slug":"caching-stored-procedures-with-out-params-is-not-s","errorCode":null,"errorMessage":"Caching stored procedures with OUT params is not supported.  Please configure useCache=false in ${statementId} statement.","messagePattern":"Caching stored procedures with OUT params is not supported\\.  Please configure useCache=false in (.+?) statement\\.","errorType":"exception","errorClass":"ExecutorException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/executor/CachingExecutor.java","lineNumber":139,"sourceCode":"    tcm.commit();\n  }\n\n  @Override\n  public void rollback(boolean required) throws SQLException {\n    try {\n      delegate.rollback(required);\n    } finally {\n      if (required) {\n        tcm.rollback();\n      }\n    }\n  }\n\n  private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {\n    if (ms.getStatementType() == StatementType.CALLABLE) {\n      for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {\n        if (parameterMapping.getMode() != ParameterMode.IN) {\n          throw new ExecutorException(\n              \"Caching stored procedures with OUT params is not supported.  Please configure useCache=false in \"\n                  + ms.getId() + \" statement.\");\n        }\n      }\n    }\n  }\n\n  @Override\n  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {\n    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);\n  }\n\n  @Override\n  public boolean isCached(MappedStatement ms, CacheKey key) {\n    return delegate.isCached(ms, key);\n  }\n\n  @Override","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/executor/CachingExecutor.java#L121-L157","documentation":"CachingExecutor (second-level cache decorator) validates via ensureNoOutParams(): if a CALLABLE statement (stored procedure) has any parameter mapping with mode != IN (OUT or INOUT), and the statement is executed on a cache-enabled path, it throws ExecutorException 'Caching stored procedures with OUT params is not supported. Please configure useCache=false in <statementId> statement.' Rationale: OUT parameters are populated per call on the caller's parameter object; a cached result cannot fill them, so caching would silently return wrong OUT values.","triggerScenarios":"A <select statementType=\"CALLABLE\"> with #{param, mode=OUT, jdbcType=...} mappings executed while second-level cache is enabled for that statement (useCache defaults true); enabling cacheEnabled=true globally and calling existing stored-procedure selects with OUT params; upgrading configs where cache was toggled on.","commonSituations":"Calling stored procedures that return values via output parameters (SQL Server/Oracle style) after enabling <setting name=\"cacheEnabled\" value=\"true\"/> or adding <cache/> to a mapper; reusing mapper XML from a non-cached setup in a cached one.","solutions":["Set useCache=\"false\" on the CALLABLE statement, exactly as the message instructs","Alternatively return procedure results as a result set (SELECT inside the proc) instead of OUT params if you want caching semantics","Scope <cache> declarations so procedure-heavy mappers are not cached","Verify with the parameterMappings that modes are declared correctly — an accidental mode=OUT on an input-only param also triggers this"],"exampleFix":"<!-- before -->\n<select id=\"getCount\" statementType=\"CALLABLE\" resultType=\"int\">\n  { call get_user_count(#{userId, mode=IN}, #{count, mode=OUT, jdbcType=INTEGER}) }\n</select>\n\n<!-- after -->\n<select id=\"getCount\" statementType=\"CALLABLE\" resultType=\"int\" useCache=\"false\">\n  { call get_user_count(#{userId, mode=IN}, #{count, mode=OUT, jdbcType=INTEGER}) }\n</select>","handlingStrategy":"validation","validationCode":"// Before enabling second-level cache on a mapper, scan its CALLABLE statements:\nConfiguration cfg = configuration;\nfor (MappedStatement ms : cfg.getMappedStatements().values()) {\n  if (ms.getStatementType() == StatementType.CALLABLE && ms.isUseCache()) {\n    for (ParameterMapping pm : ms.getBoundSql(null).getParameterMappings()) {\n      if (pm.getMode() != ParameterMode.IN) {\n        throw new IllegalStateException(ms.getId() + \" needs useCache=false (OUT params)\");\n      }\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  sqlSession.selectOne(\"callGetCount\", params);\n} catch (ExecutorException e) {\n  if (e.getMessage().contains(\"OUT params is not supported\")) {\n    // set useCache=false on the named statement, or disable caching for that mapper\n  } else throw e;\n}","preventionTips":["Mark every CALLABLE statement with OUT/INOUT params useCache=\"false\"","Audit mappers when enabling cacheEnabled or adding <cache/>","Prefer result-set-returning procedures when caching is desired"],"tags":["mybatis","second-level-cache","stored-procedure","out-params","configuration"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}