【背景】
希望select语句执行时,都能自动拼接del_flag和dept_id查询条件。
但现表结构混乱,并不是所有表都存在这两个字段,且并不是都叫这两个名字。
【执行逻辑】
1.项目启动时,把当前项目所连表中,需要字段读出来,
2.select语句过来时,查当前表是否存在要拼接字段,存在则做拼接
【代码实现】
第一步:
import com.yushu.base.common.utils.RedisUtils;
import com.yushu.base.framework.constant.ColumnConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Slf4j
@SpringBootApplication
public class DatabaseMetadataLoaderApplication implements CommandLineRunner {
@Autowired
private DataSource dataSource;
private final static List<String> COLUMN_LIST = ColumnConstant.LIST;
@Override
public void run(String... args) throws Exception {
long start = System.currentTimeMillis();
// 加载所有表和字段信息
loadDatabaseMetadata();
long end = System.currentTimeMillis();
System.out.println("[DatabaseMetadataLoaderApplication]处理时长:" + (end - start));
}
/**
* 获取所有表和字段信息
*
* @throws SQLException
*/
public void loadDatabaseMetadata() throws SQLException {
// 获取数据库连接
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
// 获取所有表
try (ResultSet tables = metaData.getTables(connection.getCatalog(), null, "%", new String[]{"TABLE"})) {
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
StringBuilder value = new StringBuilder();
// 获取当前表的所有字段
try (ResultSet columnsResultSet = metaData.getColumns(connection.getCatalog(), null, tableName, "%")) {
while (columnsResultSet.next()) {
String columnName = columnsResultSet.getString("COLUMN_NAME");
if (COLUMN_LIST.contains(columnName)) {
value.append(columnName).append(",");
}
}
}
log.info("[DatabaseMetadataLoaderApplication]TABLE_NAME:{},COLUMN_NAME:{}", tableName, value);
RedisUtils.set(tableName, value.toString().endsWith(",") ? value.substring(0, value.length() - 1) : value);
}
}
}
}
}
第二步:
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.yushu.base.common.utils.CollectionUtil;
import com.yushu.base.common.utils.RedisUtils;
import com.yushu.base.common.utils.SecurityUtils;
import com.yushu.base.framework.constant.ColumnConstant;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SubSelect;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.List;
import java.util.Properties;
@Slf4j
@Component
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisSqlLogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(statementHandler, "delegate");
MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
SqlCommandType commandType = mappedStatement.getSqlCommandType();
if (SqlCommandType.SELECT.equals(commandType)) {
BoundSql boundSql = statementHandler.getBoundSql();
Statement statement = CCJSqlParserUtil.parse(boundSql.getSql());
PlainSelect selectBody = (PlainSelect) ((Select) statement).getSelectBody();
FromItem fromItem = selectBody.getFromItem();
if (fromItem instanceof Table) {
Table table = (Table) fromItem;
String tableName = table.getName();
String tableAlias = table.getAlias() == null ? null : table.getAlias().getName();
System.out.println("=====" + tableName + " " + tableAlias);
buildWhereClause(selectBody, getDataFilter(tableName, tableAlias));
} else if (fromItem instanceof SubSelect) {
// 子查询的先不管了,历史遗留查询逻辑保持原样,新逻辑不开发子查询
// 正常如果是子查询,应递归处理
// SubSelect subSelect = (SubSelect) fromItem;
// handleSelectBody(subSelect.getSelectBody());
}
ReflectUtil.setFieldValue(boundSql, "sql", statement.toString());
}
return invocation.proceed();
}
private String getDataFilter(String tableName, String tableAlias) {
// 项目启动时,读取所有表结构,记录表名与字段的关联关系,放入缓存
// 以下校验表中是否存在需拼接字段,若存在则进行sql拼接
String columnStr = RedisUtils.getStr(tableName);
if (StringUtils.isEmpty(columnStr)) {
return "";
}
StringBuilder stringBuilder = new StringBuilder();
List<String> columnList = CollectionUtil.toList(columnStr.split(","));
for (String column : columnList) {
if (stringBuilder.length() > 0) {
stringBuilder.append("and ");
}
switch (column) {
// 3.组装sql
case ColumnConstant.DEL_FLAG:
stringBuilder.append(" ").append(getTableAlias(tableAlias)).
append(ColumnConstant.DEL_FLAG).append(" = ").append("0").append(" ");
break;
case ColumnConstant.SCHOOL_ID:
stringBuilder.append(" ").append(getTableAlias(tableAlias)).
append(ColumnConstant.SCHOOL_ID).append(" = ").append(SecurityUtils.getDeptId()).append(" ");
break;
default:
}
}
return stringBuilder.toString();
}
private String getTableAlias(String tableAlias) {
if (StringUtils.isEmpty(tableAlias)) {
return "";
}
return tableAlias + ".";
}
/**
* 添加查询条件
*
* @param select
* @param dataFilter
* @throws JSQLParserException
*/
private void buildWhereClause(PlainSelect select, String dataFilter) throws JSQLParserException {
if (StringUtils.isBlank(dataFilter)) {
return;
}
if (select.getWhere() == null) {
select.setWhere(CCJSqlParserUtil.parseCondExpression(dataFilter));
} else {
AndExpression and = new AndExpression(
CCJSqlParserUtil.parseCondExpression(dataFilter), select.getWhere());
select.setWhere(and);
}
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}
==================== 遇到的问题 ====================
【问题描述】
程序执行报错 module java.base does not "opens java.lang.reflect" to unnamed module
【原因分析】
Java9及以上版本中引入的Java Platform Module System引起的,特别是强封装的实现。它仅在特定条件下允许access,对于反射导致异常的代码尝试使用相同的限制。
【解决方案】
增加如下虚拟运行参数
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
17:18:24.648 [restartedMain] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - [refresh,591] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwTeacherInfoController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sysUserServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sysConfigServiceImpl': Invocation of init method failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h accessible: module java.base does not "opens java.lang.reflect" to unnamed module @79defdc
### The error may exist in file [F:\Java\yushu-system\target\classes\mapper\system\SysConfigMapper.xml]
### The error may involve com.yushu.base.system.mapper.SysConfigMapper.selectConfigList
### The error occurred while executing a query
### Cause: java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h accessible: module java.base does not "opens java.lang.reflect" to unnamed module @79defdc
17:18:24.650 [restartedMain] INFO c.a.d.p.DruidDataSource - [close,2105] - {dataSource-1} closing ...
17:18:24.655 [restartedMain] INFO c.a.d.p.DruidDataSource - [close,2178] - {dataSource-1} closed
17:18:24.670 [restartedMain] INFO o.a.c.c.StandardService - [log,173] - Stopping service [Tomcat]
17:18:24.704 [restartedMain] ERROR o.s.b.SpringApplication - [reportFailure,870] - Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwTeacherInfoController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sysUserServiceImpl': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sysConfigServiceImpl': Invocation of init method failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h accessible: module java.base does not "opens java.lang.reflect" to unnamed module @79defdc
### The error may exist in file [F:\Java\yushu-system\target\classes\mapper\system\SysConfigMapper.xml]
### The error may involve com.yushu.base.system.mapper.SysConfigMapper.selectConfigList
### The error occurred while executing a query
### Cause: java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.lang.reflect.InvocationHandler java.lang.reflect.Proxy.h accessible: module java.base does not "opens java.lang.reflect" to unnamed module @79defdc
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:332)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:453)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:343)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1370)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1359)
at com.yushu.base.YushuApplication.main(YushuApplication.java:22)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
【问题描述】
部分sql不需要自动拼接上述查询条件。比如登录时查询用户信息,此时还没有dept_id;或者定时任务执行时也无需拼接dept_id字段
【解决方案】
以登录为例:
设置登录标志,为用户登录的线程上下文添加一个标志,在 MyBatis 拦截器中判断当前是否为登录场景。
步骤:
-
在登录请求处理时设置标志。
-
在拦截器中检查标志,决定是否跳过部门 ID 的拼接。
代码示例:
登录请求时:
public ResponseEntity<?> login(LoginRequest loginRequest) {
try {
LoginContext.setIsLoginRequest(true); // 设置为登录请求
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
// 登录成功后清除标志
LoginContext.clear();
return ResponseEntity.ok(new JwtResponse(...));
} catch (Exception e) {
LoginContext.clear();
throw e;
}
}
线程上下文工具类:
public class LoginContext {
private static final ThreadLocal<Boolean> IS_LOGIN_REQUEST = new ThreadLocal<>();
public static void setIsLoginRequest(boolean isLogin) {
IS_LOGIN_REQUEST.set(isLogin);
}
public static boolean isLoginRequest() {
return IS_LOGIN_REQUEST.get() != null && IS_LOGIN_REQUEST.get();
}
public static void clear() {
IS_LOGIN_REQUEST.remove();
}
}
拦截器中判断:
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (LoginContext.isLoginRequest()) {
return invocation.proceed(); // 登录请求,直接放行
}
// 正常 SQL 处理逻辑
handleDepartmentId(...);
return invocation.proceed();
}