SoFunction
Updated on 2025-03-04

How to implement data permission filtering by Mybatis interceptor

background

The current project leader resigned at the end of last year, which has led to the data filtering function of the preliminary planning being not implemented.

Now the project is about to enter the trial operation period, and the data needs to be filtered according to the configuration of user data permissions.

If it is too much work to modify SQL manually in files, I will consider processing the query SQL through Mybatis later.

Basic knowledge

Introduction to Mybatis Interceptor

Interceptor interface source code analysis

package ;  
  
import ;  
  
public interface Interceptor {  
	
    Object intercept(Invocation invocation) throws Throwable;  

    default Object plugin(Object target) {  
        return (target, this);  
    }  
    
    default void setProperties(Properties properties) {  
    }  
}
  • interceptmethod
    This method is core and will be executed when intercepted to the call. The Invocation object contains all the information of the intercepted method, including the method itself, parameters, target object, etc. In this method, you can do any preprocessing or postprocessing logic, and then continue to execute the original method by calling () or directly return the custom result.
  • pluginmethod
    This method is used to decide whether to apply an interceptor to an object. If the target is returned, it means that no intercept is performed; if a new object is returned, it means that the new object will be used instead of the original object, usually a proxy object is returned here.
  • setPropertiesmethod
    Used to set the properties of the interceptor, which can be defined in the MyBatis configuration file.

Signature annotation source code analysis

@Documented  
@Retention()  
@Target({})  
public @interface Signature {  
	Class<?> type();

	String method();

	Class<?>[] args();
}
  • type: represents the type of the target object,
  • method: The name of the target method to be intercepted.
  • args: A list of parameter types representing the target method. Different @Signature annotations may have different parameter types lists, depending on the specific method signature.

Code combat

Implement a similarPageHelperA tool class that stores data permission-related information in local thread variables

public class DataAccessMethod {  
    private static final ThreadLocal<DataAccessType[]> ACCESS_LOCAL = new ThreadLocal<>();  
  
  
    public DataAccessMethod() {  
  
    }  
    public static void setLocalAccess(DataAccessType... accessType) {  
        ACCESS_LOCAL.set(accessType);  
    }  
  
    public static DataAccessType[] getLocalAccess() {  
        return ACCESS_LOCAL.get();  
    }  
  
    public static void clearLocalAccess() {  
        ACCESS_LOCAL.remove();  
    }  
  
    public static void accessData(DataAccessType... accessType) {  
        setLocalAccess(accessType);  
    }  
}

accomplishInterceptorThe interface enhances SQL processing

@Intercepts({@Signature(  
        type = ,  
        method = "query",  
        args = {, , , }  
), @Signature(  
        type = ,  
        method = "query",  
        args = {, , , , , }  
)})  
@Slf4j  
@Component  
public class DataAccessFilterInterceptor implements Interceptor {  
  
  
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        if (skip()) {  
            return ();  
        }  
        MappedStatement statement = (MappedStatement) ()[0];  
        Object parameter = ()[1];  
        BoundSql boundSql = (parameter);  
        String originalSql = ();  
        Object parameterObject = ();  
        String sql = addTenantCondition(originalSql, "1", "222");  
        ("OriginalSQL:{}, After data permission replacementSQL:{}", originalSql, sql);  
  
        BoundSql newBoundSql = new BoundSql((), sql, (), parameterObject);  
        MappedStatement newStatement = copyFromMappedStatement(statement, new BoundSqlSqlSource(newBoundSql));  
        ()[0] = newStatement;  
        return ();  
    }  
  
    /**
      * Determine whether to skip
      *
      * @return Whether to skip it
      */  
    private boolean skip() {  
        DataAccessType[] localAccess = ();  
        return localAccess == null;  
    }  
  
    private String addTenantCondition(String originalSql, String depId, String alias) {  
        String field = "id";  
        if ((alias)) {  
            field = alias + "." + field;  
        }  
  
        StringBuilder sb = new StringBuilder(());  
        int index = ("where");  
        sb = new StringBuilder(originalSql);  
        if (index &lt; 0) {  
            (" where ").append(field).append(" = ").append(depId);  
        } else {  
            (index + 5, " " + field + " = " + depId + " and ");  
        }  
        return ();  
    }  
  
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {  
         builder = new ((), (), newSqlSource, ());  
        (());  
        (());  
        (());  
        (());  
        (());  
        (());  
        (());  
        (());  
        (());  
        return ();  
    }  
  
    public static class BoundSqlSqlSource implements SqlSource {  
        private final BoundSql boundSql;  
  
        public BoundSqlSqlSource(BoundSql boundSql) {  
             = boundSql;  
        }  
  
        @Override  
        public BoundSql getBoundSql(Object parameterObject) {  
            return boundSql;  
        }  
    }  
  
  
}

Summarize

The above code is just an example. In actual production, multi-table query, SQL injection and other related issues need to be considered.

These are just personal experience. I hope you can give me a reference and I hope you can support me more.