beforeGetBoundSql 和 setProperties 都是 InnerInterceptor 接口中定义的默认方法,可以用于自定义拦截器实现。
beforeGetBoundSql 方法会在 MyBatis 获取 BoundSql 对象之前被调用,可以通过该方法来修改或扩展 SQL 语句,例如添加或删除条件、修改表名等。常见的使用场景包括:
数据权限控制:在查询数据时,自动添加当前用户可访问的数据范围的 WHERE 条件。
多租户支持:在查询数据时,自动添加租户 ID 的 WHERE 条件,以区分不同租户的数据。
动态表名:根据不同的请求参数,动态修改 SQL 语句中的表名,以实现数据分片或数据隔离等功能。
需要注意的是,修改 BoundSql 对象时,需要使用 MetaObject 对象来操作属性,以保证 MyBatis 的内部状态正确。
setProperties 方法会在创建拦截器实例时被调用,可以用于初始化拦截器的属性或资源,例如加密算法、缓存对象等。常见的使用场景包括:
加密解密:对数据库中的敏感数据进行加密,查询数据时进行解密。
缓存优化:通过缓存某些查询结果来提高系统性能,可以将缓存对象作为拦截器的属性来管理。
数据库连接池:创建数据库连接池对象并缓存起来,减少每次请求时创建连接池的开销。
需要注意的是,setProperties 方法中的参数 Properties 是一个键值对集合,可以通过该对象获取配置文件中定义的属性值。在编写拦截器时,需要定义对应的属性,并在 setProperties 方法中将其赋值给拦截器的成员变量。
代码示例:
beforeGetBoundSql 和 setProperties 两个方法都是 InnerInterceptor 接口中定义的默认方法,下面分别介绍其作用和示例代码。
在 MyBatis 中,BoundSql 对象表示了一个 SQL 语句的信息,包括 SQL 语句本身和它所需要的参数。在执行 SQL 之前,MyBatis 会通过解析 XML 配置文件和 Mapper 接口,生成对应的 BoundSql 对象。
beforeGetBoundSql 方法允许开发者在获取 BoundSql 对象之前进行必要的操作,例如设置参数、修改 SQL 等。下面是一个示例代码:
public class MyInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 获取原始的 SQL 语句
String sql = boundSql.getSql();
// 对 SQL 语句进行修改,添加 WHERE 条件
if (!sql.contains("WHERE")) {
sql += " WHERE deleted = 0";
}
// 将修改后的 SQL 语句设置回去
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", sql);
}
@Override
public void setProperties(Properties properties) {
// 设置拦截器属性,例如将 deleted 字段从逻辑删除修改为物理删除
}
}
在上面的代码中,beforeQuery 方法首先获取原始的 SQL 语句,然后判断是否已经包含了 WHERE 条件,如果没有,就添加一个 WHERE 条件来控制查询的数据范围。最后,再将修改后的 SQL 语句设置回去。
需要注意的是,在修改 BoundSql 对象时,不能直接调用其方法,而是需要通过 MetaObject 对象来操作属性,这样才能保证 MyBatis 的内部状态正确。
setProperties 方法允许开发者在创建拦截器实例时对其进行配置,例如设置一些参数、初始化一些资源等。下面是一个示例代码:
public class MyInterceptor implements InnerInterceptor {
private String algorithm; // 加密算法
@Override
public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
// 在更新之前对参数进行加密
if (parameter instanceof BaseEntity) {
BaseEntity entity = (BaseEntity) parameter;
entity.encrypt(algorithm);
}
}
@Override
public void setProperties(Properties properties) {
// 设置加密算法
algorithm = properties.getProperty("algorithm");
}
}
在上面的代码中,我们定义了一个成员变量 algorithm,它表示加密算法。在 beforeUpdate 方法中,我们首先判断参数是否为 BaseEntity 类型,如果是,则调用其 encrypt 方法对数据进行加密。在 setProperties 方法中,我们获取配置文件中的 algorithm 属性,并将其设置到拦截器实例的成员变量中,以便在 beforeUpdate 方法中使用。
使用场景一:加密解密
在数据库存储敏感数据时,对数据进行加密,在查询数据时进行解密,保护数据的安全性。
public class EncryptionInterceptor implements InnerInterceptor {
private String algorithm; // 加密算法
@Override
public void beforeGetBoundSql(StatementHandler sh) {
// 获取原始的 SQL 语句
BoundSql boundSql = sh.getBoundSql();
String sql = boundSql.getSql();
// 解密 SQL 语句
String decryptedSql = decryptSql(sql);
// 将解密后的 SQL 语句设置回去
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", decryptedSql);
}
@Override
public void setProperties(Properties properties) {
// 设置加密算法
algorithm = properties.getProperty("algorithm");
}
private String decryptSql(String encryptedSql) {
// 解密 SQL 语句
// ...
}
}
在上述代码中,EncryptionInterceptor 实现了 beforeGetBoundSql 方法,在获取 BoundSql 对象之前,对 SQL 语句进行解密操作。通过 setProperties 方法设置加密算法的属性值。
需要注意的是,这只是示例代码,实际的加密解密操作需要根据具体的加密算法和业务逻辑来编写。
当需要根据条件动态修改 SQL 语句时,可以使用 beforeGetBoundSql 方法。下面是一个场景示例:
使用场景二:
假设有一个用户表 User,包含 id、name、age 等字段。现在需要实现一个分页查询功能,只返回年龄大于 18 岁的用户记录,并且查询结果按照姓名升序排序。但是,由于数据库中存储的是加密后的数据,所以需要在查询之前解密数据,再进行条件过滤和排序操作。
public class DecryptInterceptor implements InnerInterceptor {
@Override
public void beforeGetBoundSql(StatementHandler sh) {
BoundSql boundSql = sh.getBoundSql();
String sql = boundSql.getSql();
// 解密 SQL 语句
String decryptedSql = decryptSql(sql);
// 将解密后的 SQL 语句设置回去
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", decryptedSql);
// 设置查询条件
Map<String, Object> parameterObject = (Map<String, Object>) boundSql.getParameterObject();
parameterObject.put("minAge", 18);
// 修改排序方式
String originalOrderByClause = boundSql.getOrderByClause();
StringBuilder newOrderByClause = new StringBuilder();
if (StringUtils.isNotEmpty(originalOrderByClause)) {
newOrderByClause.append(originalOrderByClause).append(", ");
}
newOrderByClause.append("name ASC");
metaObject.setValue("orderByClause", newOrderByClause.toString());
}
private String decryptSql(String sql) {
// 解密 SQL 语句
// ...
}
}
在上述代码中,DecryptInterceptor 实现了 beforeGetBoundSql 方法,在获取 BoundSql 对象之前解密 SQL 语句,并设置查询条件和排序方式。使用 MetaObject 对象操作 BoundSql 对象的属性来实现修改。
需要注意的是,这只是示例代码,实际的加密解密操作需要根据具体的加密算法和业务逻辑来编写。
使用场景三:
当需要在拦截器中使用一些配置信息时,可以使用 setProperties 方法。下面是一个场景示例:
假设有一个需求:需要记录 SQL 执行时间,并根据执行时间判断 SQL 是否过慢。如果 SQL 过慢,需要记录日志并发出告警。为了方便配置告警阈值,可以将告警阈值作为拦截器的属性,在创建拦截器实例时通过配置文件进行配置。
public class SqlExecutionTimeInterceptor implements InnerInterceptor {
private long threshold; // 告警阈值,单位毫秒
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
if (executionTime >= threshold) {
log.warn("SQL 执行时间过长,耗时:{}ms", executionTime);
sendAlarm(executionTime);
}
return result;
}
@Override
public void setProperties(Properties properties) {
String thresholdStr = properties.getProperty("threshold");
threshold = Long.parseLong(thresholdStr);
}
private void sendAlarm(long executionTime) {
// 发送告警
// ...
}
}
在上述代码中,SqlExecutionTimeInterceptor 实现了 setProperties 方法,在创建拦截器实例时从配置文件中读取告警阈值,并保存到拦截器的属性中。在拦截器的 intercept 方法中,计算 SQL 执行时间,并判断是否超过告警阈值,如果超过,则发送告警。
需要注意的是,这只是示例代码,实际的告警操作需要根据具体的业务逻辑来编写。