When implementing data permission control using MyBatis-Plus, it is crucial to manage SQL parameter placeholders accurately. The error java.sql.SQLException: No value specified for parameter 4 typically arises due to improper handling of these parameters, especially when additional conditions are appended to the SQL query. This comprehensive guide addresses the root causes of this issue and provides detailed solutions to ensure seamless parameter management and robust security against SQL injection.
The primary issue stems from the addition of new conditions to the original SQL query without appropriately accounting for the corresponding parameter placeholders. In the provided scenario, an additional condition cldevice.modelLogo = 'TBMZ' is appended as a raw SQL string rather than using a parameter placeholder. This disrupts the original parameter mapping, leading to mismatches and the aforementioned SQLException.
Always use parameter placeholders (? or #{}) for dynamic SQL conditions. This practice not only maintains the integrity of parameter mappings but also safeguards against SQL injection attacks.
When combining multiple conditions, maintain a consistent order of parameters to avoid mismatches. Each placeholder should correspond accurately to its respective value in the SQL statement.
Refrain from embedding static values directly into SQL queries. Instead, pass them as parameters to ensure flexibility and security.
Instead of embedding the value 'TBMZ' directly into the SQL condition, use a parameter placeholder. This change ensures that the parameter is correctly mapped and prevents the SQL query from expecting an additional parameter.
**Original Implementation:**
return equalsTo;
**Revised Implementation:**
return new EqualsTo(
new Column(table, columnName),
new JdbcParameter()
);
By using JdbcParameter, you allow MyBatis-Plus to handle the parameter binding seamlessly.
getParameterMappings MethodEnsure that all required parameters are correctly mapped and bound. This involves retrieving the necessary user information and returning it as a parameter string.
@Override
public String getParameterMappings() {
UserInfo userInfo = UserInfoContext.get();
if (ObjectUtils.isEmpty(userInfo)) {
return null;
}
String loginId = userInfo.getLoginid();
Object userModelObj = AUTH_USER_MODULE.get(loginId);
if (!(userModelObj instanceof JSONObject)) {
return null;
}
JSONObject userModel = (JSONObject) userModelObj;
Object modelLogo = userModel.get(MODEL_LOGO);
// Return the value that should be bound to the parameter
return modelLogo != null ? modelLogo.toString() : null;
}
When combining multiple conditions, ensure that each condition uses its respective parameter placeholder. This approach maintains the integrity of parameter mapping and avoids mismatches.
@Override
public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
try {
UserInfo userInfo = UserInfoContext.get();
if (ObjectUtils.isEmpty(userInfo)) {
return null;
}
String loginId = userInfo.getLoginid();
Object userModelObj = AUTH_USER_MODULE.get(loginId);
if (!(userModelObj instanceof JSONObject)) {
return null;
}
JSONObject userModel = (JSONObject) userModelObj;
if (ObjectUtils.isEmpty(userModel.get(USER_LOGIN_ID))) {
return null;
}
String tableName = table.getName().toLowerCase();
if (!isTableNeedPermission(tableName)) {
return null;
}
Object modelLogo = userModel.get(MODEL_LOGO);
if (ObjectUtils.isEmpty(modelLogo)) {
log.debug("用户{}的model_logo为空", loginId);
return null;
}
Expression permissionCondition = buildPermissionCondition(table, MODEL_LOGO, modelLogo.toString());
if (permissionCondition == null) {
return null;
}
return where == null ? permissionCondition : new AndExpression(where, permissionCondition);
} catch (Exception e) {
log.error("构建数据权限条件异常", e);
return null;
}
}
In this revised method, the permission condition is added using a parameter placeholder, ensuring that all parameters are correctly mapped and bound during query execution.
The final SQL query should correctly incorporate all parameter placeholders, ensuring that each one is adequately mapped to its corresponding value:
SELECT id, devicename, locationid, dtypename, dmodelname, specif, dstate, remark, delflag, topicid, topicdesc, devicecode, modelid, dtypeid, parentlocationid, parenttype, devicetype, craft, parentdeviceid, importcount, version, agrflag, monflag, url, matchedflag, model_logo, model_relation, data_logo, protocal_code, offline_alarm, alarm_count, create_by, create_time, update_by, update_time
FROM cldevice
WHERE (delflag = ? AND craft = ?)
AND cldevice.modelLogo = ?
ORDER BY importcount DESC
LIMIT ?
Here, each ? corresponds to a parameter value that will be securely bound during execution, eliminating the risk of missing parameters and ensuring the query runs smoothly.
Below is the fully revised implementation of the CustomDataPermissionHandler class, which incorporates all the necessary changes to handle SQL parameters correctly:
public class CustomDataPermissionHandler implements MultiDataPermissionHandler {
private static final String MODEL_LOGO = "modelLogo";
private static final String USER_LOGIN_ID = "userLoginId";
@Override
public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
try {
UserInfo userInfo = UserInfoContext.get();
if (ObjectUtils.isEmpty(userInfo)) {
return null;
}
String loginId = userInfo.getLoginid();
Object userModelObj = AUTH_USER_MODULE.get(loginId);
if (!(userModelObj instanceof JSONObject)) {
return null;
}
JSONObject userModel = (JSONObject) userModelObj;
if (ObjectUtils.isEmpty(userModel.get(USER_LOGIN_ID))) {
return null;
}
String tableName = table.getName().toLowerCase();
if (!isTableNeedPermission(tableName)) {
return null;
}
Object modelLogo = userModel.get(MODEL_LOGO);
if (ObjectUtils.isEmpty(modelLogo)) {
log.debug("用户{}的model_logo为空", loginId);
return null;
}
Expression permissionCondition = buildPermissionCondition(table, MODEL_LOGO, modelLogo.toString());
if (permissionCondition == null) {
return null;
}
return where == null ? permissionCondition : new AndExpression(where, permissionCondition);
} catch (Exception e) {
log.error("构建数据权限条件异常", e);
return null;
}
}
private boolean isTableNeedPermission(String tableName) {
if (ObjectUtils.isEmpty(AUTH_MODULE) || ObjectUtils.isEmpty(tableName)) {
return false;
}
return AUTH_MODULE.keySet().stream()
.flatMap(key -> Arrays.stream(key.split(",")))
.map(String::trim)
.anyMatch(table -> table.equalsIgnoreCase(tableName));
}
private Expression buildPermissionCondition(Table table, String columnName, String whereVal) {
try {
Column column = new Column(table, columnName);
// Use JdbcParameter to ensure proper parameter handling
Expression rightExpression = new JdbcParameter();
EqualsTo equalsTo = new EqualsTo();
equalsTo.setLeftExpression(column);
equalsTo.setRightExpression(rightExpression);
return equalsTo;
} catch (Exception e) {
log.error("构建权限条件异常: table={}, column={}, value={}",
table.getName(), columnName, whereVal, e);
return null;
}
}
@Override
public String getParameterMappings() {
UserInfo userInfo = UserInfoContext.get();
if (ObjectUtils.isEmpty(userInfo)) {
return null;
}
String loginId = userInfo.getLoginid();
Object userModelObj = AUTH_USER_MODULE.get(loginId);
if (!(userModelObj instanceof JSONObject)) {
return null;
}
JSONObject userModel = (JSONObject) userModelObj;
Object modelLogo = userModel.get(MODEL_LOGO);
// Return the value that should be bound to the parameter
return modelLogo != null ? modelLogo.toString() : null;
}
}
This implementation ensures that:
Ensure that all input parameters are validated before being passed to the SQL query. This step adds an extra layer of security and prevents potential injection attacks.
Implement comprehensive logging to monitor the flow of parameters and SQL query construction. This practice aids in quickly identifying and resolving issues related to parameter mismatches.
Ensure that MyBatis-Plus and all related dependencies are kept up-to-date. Newer versions often include bug fixes and improvements that can prevent similar issues from arising.
The error java.sql.SQLException: No value specified for parameter 4 is indicative of improper handling of SQL parameter placeholders in the context of MyBatis-Plus data permission control. By adhering to best practices such as using parameter placeholders, maintaining parameter order, and avoiding hardcoded values, you can effectively resolve this issue. The comprehensive modifications outlined above not only address the immediate problem but also enhance the overall robustness and security of your data permission implementation.
For further reading and deeper insights into parameter handling with MyBatis-Plus, refer to the authoritative resources and documentation available on the official MyBatis-Plus website.