使用Mybatis自定义插件实现不侵入业务的公共参数自动追加

这篇具有很好参考价值的文章主要介绍了使用Mybatis自定义插件实现不侵入业务的公共参数自动追加。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

后台业务开发的过程中,往往会遇到这种场景:需要记录每条记录产生时间、修改时间、修改人及添加人,在查询时查询出来。
以往的做法通常是手动在每个业务逻辑里耦合上这么一块代码,也有更优雅一点的做法是写一个拦截器,然后在Mybatis拦截器中为实体对象中的公共参数进行赋值,但最终依然需要在业务SQL上手动添加上这几个参数,很多开源后台项目都有类似做法。

这种做法往往不够灵活,新增或修改字段时每处业务逻辑都需要同步修改,业务量大的话这么改非常麻烦。

最近在我自己的项目中写了一个Mybatis插件,这个插件能够实现不修改任何业务逻辑就能实现添加或修改时数据库公共字段的赋值,并能在查询时自动查询出来。

实现原理

Mybatis提供了一系列的拦截器,用于实现在Mybatis执行的各个阶段允许插入或修改自定义逻辑。

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

我这里用的是Executor,它能做到在所有数据库操作前后执行一些逻辑,甚至可以修改Mybatis的上下文参数后继续执行。
在Mybaits的拦截器中,可以拿到MappedStatement对象,这里面包含了一次数据库操作的原始SQL以及实体对象与结果集的映射关系,为了实现公共参数自动携带,我们就需要在拦截器中修改原始SQL:

  1. Insert操作:自动为Insert语句添加公共字段并赋值
  2. Update操作:自动为Update语句添加公共字段并赋值
  3. Select操作:自动为Select语句的查询参数上添加上公共字段

以及修改实体对象与结果集的映射关系,做到自动修改查询语句添加公共字段后能够使Mybatis将查出的公共字段值赋给实体类。

简单来说就是修改MappedStatement中的SqlSource以及ResultMap

核心思路:修改SqlSource跟ResultMap

简单说下咱们接下来要修改的两大主要对象:Mybatis的SqlSource跟ResultMap是什么:

  • SqlSource本身是一个Mybatis的接口,他有几个实现类,分别用于实现动态、静态SQL的生成等。最终底层执行器会调用SqlSource.getBoundSql()得到最终要执行的SQL,我们可以自行编写一个SqlSource的实现,在getBoundSql()中完成原始SQL的修改,添加公共参数,经过修改后的SQL就会携带我们想要的公共参数写入/更新到数据库了。

  • ResultMap是Mybatis保存实体对象与数据库表字段映射关系的结构,用于将数据库返回的结果值自动绑定到实体对象上。为了实现查询时能够自动查询出公共字段并绑定到实体对象,我们就需要遍历整个ResultMap,根据自定义的字段-实体属性的映射规则添加映射项。配合着使用SqlSource自动修改查询SQL,添加公共字段,由Mybatis根据ResultMap的规则完成属性映射,就能实现查询时自动查询出公共参数的值了。实现更新操作自动更新公共参数的方式几乎与查询操作实现公共参数携带的方式相同,只是我们不需要关注ResultMap了,仅需要修改原始的UPDATE SQL追加上公共参数的值即可。

在后面的具体实现上,我借助了一个技巧:通过直接在Mybatis拦截器中替换Mybatis的上下文Configuration对象为包含了修改后的ResultMap及SqlSource的对象,相当于内存中时存在着两套互相独立的上下文,而这个上下文只会在首次Sql请求的时候被构建生成,后续请求只会取原先的缓存对象,在几乎不影响效率的同时,带来的开销仅仅是可以忽略不计的内存占用。使用这个插件相应的也提高了开发效率,代码的简洁性也提升了不少。总的来看,使用两个上下文共存的方式能够在不影响原始Mybatis上下文的情况下,在拦截器中通过切换上下文的方式能够实现随时停用&启用公共对象自动映射,可谓是灵活性极强。

所有功能都被我浓缩到了一个文件里面,使用时直接放到项目里即可,简直不要太方便
整个插件已经开源到了GitHub,附上链接:https://github.com/Random-pro/ExtParamInterceptor 求个Star
使用Mybatis自定义插件实现不侵入业务的公共参数自动追加

修改SqlSource

在SqlSource中,包含了原始待执行的SQL,需要将它修改为携带公共参数的SQL。
需要注意的是Mybatis的SqlSource、ResultMap中的属性仅允许初次构造SqlSource对象时进行赋值,后续如果需要修改只能通过反射或者新构造一个对象替换旧对象的方式进行内部参数修改。

直接贴出来代码,这里新构造了SqlSource对象,在里面实现了原始SQL的解析修改:
SQL的动态修改使用了JSQLParser将原始SQL解析为AST抽象语法树后做参数追加,之后重新解析为SQL,使用自定义SqlSource返回修改后的SQL实现SQL修改

static class ModifiedSqlSourceV2 implements SqlSource {
        private final MappedStatement mappedStatement;
        private final Configuration configuration;

        public ModifiedSqlSourceV2(MappedStatement mappedStatement, Configuration configuration) {
            this.mappedStatement = mappedStatement;
            this.configuration = configuration;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            // 获取原始的 BoundSql 对象
            BoundSql originalBoundSql = mappedStatement.getSqlSource().getBoundSql(parameterObject);

            // 获取原始的 SQL 字符串
            String originalSql = originalBoundSql.getSql();
            log.debug("公共参数添加 - 修改前SQL:{}", originalSql);

            // 创建新的 BoundSql 对象
            String modifiedSql;
            try {
                modifiedSql = buildSql(originalSql);
                log.debug("公共参数添加 - 修改后SQL:{}", modifiedSql);
            } catch (JSQLParserException e) {
                log.error("JSQLParser解析修改SQL添加公共参数失败, 继续使用原始SQL执行" , e);
                modifiedSql = originalSql;
            }
            BoundSql modifiedBoundSql = new BoundSql(configuration, modifiedSql,
                    originalBoundSql.getParameterMappings(), parameterObject);
            // 复制其他属性
            originalBoundSql.getAdditionalParameters().forEach(modifiedBoundSql::setAdditionalParameter);
            modifiedBoundSql.setAdditionalParameter("_parameter", parameterObject);

            return modifiedBoundSql;
        }

        private String buildSql(String originalSql) throws JSQLParserException {
            Statement statement = CCJSqlParserUtil.parse(originalSql);

            switch(mappedStatement.getSqlCommandType()) {
                case INSERT -> {
                    if(statement instanceof Insert insert) {
                        insert.addColumns(new Column(CREATE_BY_COLUMN), new Column(CREATE_TIME_COLUMN));
                        ExpressionList expressionList = insert.getItemsList(ExpressionList.class);
                        Timestamp currentTimeStamp = new Timestamp(System.currentTimeMillis());

                        if (!expressionList.getExpressions().isEmpty()) {
                            // 多行插入 行构造器解析
                            if (expressionList.getExpressions().get(0) instanceof RowConstructor) {
                                expressionList.getExpressions().forEach((expression -> {
                                    if (expression instanceof RowConstructor rowConstructor) {
                                        rowConstructor.getExprList().getExpressions().add(new StringValue(getCurrentUser()));
                                        rowConstructor.getExprList().getExpressions().add(new TimestampValue().withValue(currentTimeStamp));
                                    }
                                }));
                            } else {
                                // 其余默认单行插入
                                expressionList.addExpressions(new StringValue(getCurrentUser()), new TimestampValue().withValue(currentTimeStamp));
                            }
                        }

                        return insert.toString();
                    }
                }
                case UPDATE -> {
                    if(statement instanceof Update update) {
                        List<UpdateSet> updateSetList = update.getUpdateSets();
                        UpdateSet updateBy = new UpdateSet(new Column(UPDATE_BY_COLUMN), new StringValue(getCurrentUser()));
                        Timestamp currentTimeStamp = new Timestamp(System.currentTimeMillis());
                        UpdateSet updateTime = new UpdateSet(new Column(UPDATE_TIME_COLUMN), new TimestampValue().withValue(currentTimeStamp));
                        updateSetList.add(updateBy);
                        updateSetList.add(updateTime);

                        return update.toString();
                    }
                }
                case SELECT -> {
                    if(statement instanceof Select select) {
                        SelectBody selectBody = select.getSelectBody();
                        if(selectBody instanceof PlainSelect plainSelect) {
                            TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
                            List<String> tableNames = tablesNamesFinder.getTableList(select);

                            List<SelectItem> selectItems = plainSelect.getSelectItems();
                            tableNames.forEach((tableName) -> {
                                String lowerCaseTableName = tableName.toLowerCase();
                                selectItems.add(new SelectExpressionItem().withExpression(new Column(new Table(tableName), CREATE_BY_COLUMN)).withAlias(new Alias(lowerCaseTableName + "_" + CREATE_BY_COLUMN)));
                                selectItems.add(new SelectExpressionItem().withExpression(new Column(new Table(tableName), CREATE_TIME_COLUMN)).withAlias(new Alias(lowerCaseTableName + "_" + CREATE_TIME_COLUMN)));
                                selectItems.add(new SelectExpressionItem().withExpression(new Column(new Table(tableName), UPDATE_BY_COLUMN)).withAlias(new Alias(lowerCaseTableName + "_" + UPDATE_BY_COLUMN)));
                                selectItems.add(new SelectExpressionItem().withExpression(new Column(new Table(tableName), UPDATE_TIME_COLUMN)).withAlias(new Alias(lowerCaseTableName + "_" + UPDATE_TIME_COLUMN)));
                            });

                            return select.toString();
                        }
                    }
                }
                default -> {
                    return originalSql;
                }
            }
            return originalSql;
        }
}

修改ResultMap

ResultMap中存放了结果列与映射实体类属性的对应关系,这里为了自动生成公共属性的结果映射,直接根据当前ResultMap中存储的结果映射实体类的名称作为表名,自动建立与结果列的映射关系。

就是说数据库表对应的实体类的名字需要与数据库表保持一致(但是实体类名可以是数据库表的名字的驼峰命名,如表user_role的实体类需要命名为UserRole),只要遵守这个命名规则即可实现查询结果中自动携带公共参数值
如下为添加公共参数结果映射的代码

private static List<ResultMapping> addResultMappingProperty(Configuration configuration, List<ResultMapping> resultMappingList, Class<?> mappedType) {
        // resultMappingList为不可修改对象
        List<ResultMapping> modifiableResultMappingList = new ArrayList<>(resultMappingList);

        String []checkList = {CREATE_BY_PROPERTY, CREATE_TIME_PROPERTY, UPDATE_BY_PROPERTY, UPDATE_TIME_PROPERTY};
        boolean hasAnyTargetProperty = Arrays.stream(checkList).anyMatch((property) -> ReflectionUtils.findField(mappedType, property) != null);

        // 用于防止映射目标为基本类型却被添加映射 导致列名规则 表名_列名 无法与映射的列名的添加规则 映射类型名_列名 相照应
        // 从而导致映射类型为基本类型时会生成出类似与string_column1的映射名 而产生找不到映射列名与实际结果列相照应的列名导致mybatis产生错误
        // 规则: 仅映射类型中包含如上四个字段其一时才会添加映射
        if(hasAnyTargetProperty) {
            // 支持类型使用驼峰命名
            String currentTable = upperCamelToLowerUnderscore(mappedType.getSimpleName());

            // 映射方式 表名_公共字段名 在实体中 表名与实体名相同 则可完成映射
            modifiableResultMappingList.add(new ResultMapping.Builder(configuration, CREATE_BY_PROPERTY, currentTable + "_" + CREATE_BY_COLUMN, String.class).build());
            modifiableResultMappingList.add(new ResultMapping.Builder(configuration, CREATE_TIME_PROPERTY, currentTable + "_" + CREATE_TIME_COLUMN, Timestamp.class).build());
            modifiableResultMappingList.add(new ResultMapping.Builder(configuration, UPDATE_BY_PROPERTY, currentTable + "_" + UPDATE_BY_COLUMN, String.class).build());
            modifiableResultMappingList.add(new ResultMapping.Builder(configuration, UPDATE_TIME_PROPERTY, currentTable + "_" + UPDATE_TIME_COLUMN, Timestamp.class).build());
        }

        return modifiableResultMappingList;
}

构建MappedStatement

原本的由Mybatis创建的MappedStatement无法直接修改,因此这里手动通过ResultMap.Builder()构造一个新的MappedStatement,同时保持其余参数不变,只替换SqlSource、ResultMap为先前重新创建的对象。

public MappedStatement buildMappedStatement(Configuration newModifiedConfiguration, MappedStatement mappedStatement) {
        SqlSource modifiedSqlSource = new ModifiedSqlSourceV2(mappedStatement, newModifiedConfiguration);

        List<ResultMap> modifiedResultMaps = mappedStatement.getResultMaps().stream().map((resultMap) -> {
            List<ResultMapping> resultMappingList = resultMap.getResultMappings();
            // 为每个resultMap中的resultMappingList添加公共参数映射
            List<ResultMapping> modifiedResultMappingList = addResultMappingProperty(newModifiedConfiguration, resultMappingList, resultMap.getType());

            return new ResultMap.Builder(newModifiedConfiguration, resultMap.getId(), resultMap.getType(), modifiedResultMappingList, resultMap.getAutoMapping()).build();
        }).toList();

        // 构造新MappedStatement 替换SqlSource、ResultMap、Configuration
        MappedStatement.Builder newMappedStatementBuilder = new MappedStatement.Builder(newModifiedConfiguration, mappedStatement.getId(), modifiedSqlSource, mappedStatement.getSqlCommandType())
                .cache(mappedStatement.getCache()).databaseId(mappedStatement.getDatabaseId()).dirtySelect(mappedStatement.isDirtySelect()).fetchSize(mappedStatement.getFetchSize())
                .flushCacheRequired(mappedStatement.isFlushCacheRequired())
                .keyGenerator(mappedStatement.getKeyGenerator())
                .lang(mappedStatement.getLang()).parameterMap(mappedStatement.getParameterMap()).resource(mappedStatement.getResource()).resultMaps(modifiedResultMaps)
                .resultOrdered(mappedStatement.isResultOrdered())
                .resultSetType(mappedStatement.getResultSetType()).statementType(mappedStatement.getStatementType()).timeout(mappedStatement.getTimeout()).useCache(mappedStatement.isUseCache());
        if(mappedStatement.getKeyColumns() != null) {
            newMappedStatementBuilder.keyColumn(StringUtils.collectionToDelimitedString(Arrays.asList(mappedStatement.getKeyColumns()), ","));
        }
        if(mappedStatement.getKeyProperties() != null) {
            newMappedStatementBuilder.keyProperty(StringUtils.collectionToDelimitedString(Arrays.asList(mappedStatement.getKeyProperties()), ","));
        }
        if(mappedStatement.getResultSets() != null) {
            newMappedStatementBuilder.resultSets(StringUtils.collectionToDelimitedString(Arrays.asList(mappedStatement.getResultSets()), ","));
        }
        return newMappedStatementBuilder.build();
}

到这里为止,已经完全实现了修改原始SQL、修改结果映射的工作了,将修改后的MappedStatement对象往下传入到invoke()即可但是还能改进。

改进

在Mybatis拦截器中可以通过MappedStatement.getConfiguration()拿到整个Mybatis的上下文,在这个里面可以拿到所有Mybatis的所有SQL操作的映射结果以及SQL,可以一次性修改完后,将Configuration作为一个缓存使用,每次有请求进入拦截器后就从Configuration获取被修改的MappedStatement后直接invoke,效率会提升不少。
经给改进后,除了应用启动后执行的第一个SQL请求由于需要构建Configuration会慢一些,之后的请求几乎没有产生性能方面的影响。

现在唯一的性能消耗是每次执行请求前Mybatis会调用我们自己重新定义的SqlSource.getBoundSql()将原始SQL解析为AST后重新构建生成新SQL的过程了,这点开销几乎可忽略不计。如果想更进一步的优化,可以考虑将原始SQL做key,使用Caffeine、Guava缓存工具等方式将重新构建后的查询SQL缓存起来(Update/Insert由于追加有时间参数的原因,不能被缓存),避免多次重复构建SQL带来的开销

完整实现&开源地址

经过优化后,整个插件已经比较完善了,能够满足日常使用,无论是单表查询,还是多表联查,嵌套查询都能够实现无侵入的参数追加,目前仅实现了创建人、创建时间、修改人、修改时间的参数追加&映射绑定,如有需要的可以自行修改。

我把它放到了GitHub上,并附带有示例项目:https://github.com/Random-pro/ExtParamInterceptor
觉得好用的欢迎点个Star
使用Mybatis自定义插件实现不侵入业务的公共参数自动追加

插件使用示例

所有的新增操作均会被自动添加创建人、创建时间。更新操作则会被自动添加更新人、更新时间。正常使用Mybatis操作即可,与原先无任何差别就不在这里给出示例了,如果需要示例请前往我在GitHub上的示例项目。

  1. 单表查询

    // 实体类Child(类名对应具体的表名 使用驼峰命名法,如表名为user_role,则类名应写为UserRole)
    @Data
    public class Child extends BaseDomain {
      private int childId;
      private int parentId;
      private String childName;
      private String path;
    }
    
    // 公共字段
    @Data
    public class BaseDomain {
      private String createBy;
      private Date createTime;
      private String updateBy;
      private Date updateTime;
    }
    
    // Mapper接口
    @Mapper
    public interface TestMapper {
      @Select("SELECT id as childId, name as childName, parent_id as parentId, path FROM child")
      List<Child> getChildList();
    }
    
    // Controller
    @RestController
    @RequestMapping("user")
    public record UserController(TestMapper testMapper) {
      @GetMapping("getChildList")
      public List<Child> getChildList() {
        return testMapper.getChildList();
      }
    }
    

    访问user/getChildList获取结果:

    [
        {
            "createBy": "sun11",
            "createTime": "2023-12-18T07:58:58.000+00:00",
            "updateBy": "random",
            "updateTime": "2023-12-18T07:59:19.000+00:00",
            "childId": 1,
            "parentId": 1,
            "childName": "childName1_1",
            "path": "childPath1_1"
        },
        {
            "createBy": "sun12",
            "createTime": "2023-12-18T07:58:59.000+00:00",
            "updateBy": "RANDOM",
            "updateTime": "2023-12-18T07:59:20.000+00:00",
            "childId": 2,
            "parentId": 1,
            "childName": "childName1_2",
            "path": "childPath1_2"
        },
        {
            "createBy": "sun21",
            "createTime": "2023-12-18T07:59:00.000+00:00",
            "updateBy": "randompro",
            "updateTime": "2023-12-18T07:59:21.000+00:00",
            "childId": 3,
            "parentId": 2,
            "childName": "childName2_1",
            "path": "childPath2_2"
        }
    ]
    
  2. 多表查询

    // 实体类Base(类名对应具体的表名 使用驼峰命名法,如表名为user_role,则类名应写为UserRole) 注意:当关联多个表时,需要取哪个表里的公共字段(创建人、创建时间等字段)则将映射实体类名命名为该表的表名
    @Data
    public class Base extends BaseDomain {
      private int id;
      private String baseName;
      private String basePath;
      private List<Child> pathChildList;
    }
    
    @Data
    public class Child extends BaseDomain {
      private int childId;
      private int parentId;
      private String childName;
      private String path;
    }
    
    // 公共字段
    @Data
    public class BaseDomain {
      private String createBy;
      private Date createTime;
      private String updateBy;
      private Date updateTime;
    }
    
    // Mapper接口
    @Mapper
    public interface TestMapper {
      @Select("SELECT BASE.ID as id , BASE.BASE_NAME as baseName, CHILD.PATH as basePath FROM BASE, CHILD WHERE BASE.ID = CHILD.PARENT_ID")
      List<Base> getBaseAndChildPath();
    }
    
    // Controller
    @RestController
    @RequestMapping("user")
    public record UserController(TestMapper testMapper) {
      @GetMapping("getBaseAndChildPath")
      public List<Base> getBaseAndChildPath() {
        return testMapper.getBaseAndChildPath();
      }
    }
    

    访问user/getBaseAndChildPath获取结果:

    [
        {
            "createBy": "sun_base",
            "createTime": "2023-12-18T07:59:29.000+00:00",
            "updateBy": "random_base",
            "updateTime": "2023-12-18T08:00:09.000+00:00",
            "id": 1,
            "baseName": "baseName1",
            "basePath": "childPath1_1",
            "pathChildList": null
        },
        {
            "createBy": "sun_base",
            "createTime": "2023-12-18T07:59:29.000+00:00",
            "updateBy": "random_base",
            "updateTime": "2023-12-18T08:00:09.000+00:00",
            "id": 1,
            "baseName": "baseName1",
            "basePath": "childPath1_2",
            "pathChildList": null
        },
        {
            "createBy": "sun2_base",
            "createTime": "2023-12-18T07:59:30.000+00:00",
            "updateBy": "randompro_base",
            "updateTime": "2023-12-18T08:00:09.000+00:00",
            "id": 2,
            "baseName": "baseName2",
            "basePath": "childPath2_2",
            "pathChildList": null
        }
    ]
    
  3. 多表嵌套查询

    // 实体类Base(类名对应具体的表名 使用驼峰命名法,如表名为user_role,则类名应写为UserRole) 嵌套查询中使用到的多个实体若均可映射到对应表中的如上四个字段的值(只要该实体通过继承、直接添加的方式获取到了以上声明的四个实体属性的getter/setter方法即可)
    @Data
    public class Base extends BaseDomain {
      private int id;
      private String baseName;
      private String basePath;
      private List<Child> pathChildList;
    }
    
    @Data
    public class Child extends BaseDomain {
      private int childId;
      private int parentId;
      private String childName;
      private String path;
    }
    
    // 公共字段
    @Data
    public class BaseDomain {
      private String createBy;
      private Date createTime;
      private String updateBy;
      private Date updateTime;
    }
    
    // Mapper接口
    @Mapper
    public interface TestMapper {
      List<Base> getPathList();
    }
    
    // Controller
    @RestController
    @RequestMapping("user")
    public record UserController(TestMapper testMapper) {
      @GetMapping("getPathList")
      public List<Base> getPathList() {
        return testMapper.getPathList();
      }
    }
    

    Mapper.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.live.mapper.TestMapper">
    
        <resultMap type="com.live.domian.Base" id="PathDomainMap">
            <result property="id"    column="id"    />
            <result property="baseName" column="base_name"/>
            <result property="basePath" column="base_path"/>
    
            <collection property="pathChildList" ofType="com.live.domian.Child">
                <id property="childId" column="child_id"/>
                <result property="parentId" column="parent_id"/>
                <result property="childName" column="child_name"/>
                <result property="path" column="path"/>
            </collection>
        </resultMap>
    
        <select id="getPathList" resultMap="PathDomainMap">
            SELECT base.id, base.base_name, base.base_path, child.id AS child_id, child.name AS child_name,
                  child.path, child.parent_id FROM base LEFT JOIN child ON base.id = child.parent_id
        </select>
    </mapper>
    

    访问user/getPathList获取结果,可见嵌套查询中每个层次都取到了公共字段createBy、createTime、updateBy、updateTime的值:

    [
        {
            "createBy": "sun_base",
            "createTime": "2023-12-18T07:59:29.000+00:00",
            "updateBy": "random_base",
            "updateTime": "2023-12-18T08:00:09.000+00:00",
            "id": 1,
            "baseName": "baseName1",
            "basePath": "basePath1",
            "pathChildList": [
                {
                    "createBy": "sun12",
                    "createTime": "2023-12-18T07:58:59.000+00:00",
                    "updateBy": "RANDOM",
                    "updateTime": "2023-12-18T07:59:20.000+00:00",
                    "childId": 2,
                    "parentId": 1,
                    "childName": "childName1_2",
                    "path": "childPath1_2"
                },
                {
                    "createBy": "sun11",
                    "createTime": "2023-12-18T07:58:58.000+00:00",
                    "updateBy": "random",
                    "updateTime": "2023-12-18T07:59:19.000+00:00",
                    "childId": 1,
                    "parentId": 1,
                    "childName": "childName1_1",
                    "path": "childPath1_1"
                }
            ]
        },
        {
            "createBy": "sun2_base",
            "createTime": "2023-12-18T07:59:30.000+00:00",
            "updateBy": "randompro_base",
            "updateTime": "2023-12-18T08:00:09.000+00:00",
            "id": 2,
            "baseName": "baseName2",
            "basePath": "basePath2",
            "pathChildList": [
                {
                    "createBy": "sun21",
                    "createTime": "2023-12-18T07:59:00.000+00:00",
                    "updateBy": "randompro",
                    "updateTime": "2023-12-18T07:59:21.000+00:00",
                    "childId": 3,
                    "parentId": 2,
                    "childName": "childName2_1",
                    "path": "childPath2_2"
                }
            ]
        }
    ]
    

    嵌套查询中,如果只希望获取到特定的表的那四个公共属性,则把不希望获取公共属性的表对应的实体类中的四个映射属性去掉(若使用BaseDomain继承来的四个属性的的话去掉继承BaseDomain)即可文章来源地址https://www.toymoban.com/news/detail-760643.html

到了这里,关于使用Mybatis自定义插件实现不侵入业务的公共参数自动追加的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot 使用MyBatis分页插件实现分页功能

    案例地址: https://gitee.com/vinci99/paging-pagehelper-demo/tree/master 这里使用application.properties类型配置文件来做例子 创建一个持久化对象TestUserPO 编写在Mapper中编写SQL语句查询与之对应的表 在业务层调用Mapper接口获取数据并分页;需要注意:PageHelper.startPage(pageNum,pageSize)必须写在查询

    2024年02月10日
    浏览(49)
  • 自定义Mybatis-plus插件(限制最大查询数量)

    需求背景 ​一次查询如果结果返回太多(1万或更多),往往会导致系统性能下降,有时更会内存不足,影响系统稳定性,故需要做限制。 解决思路 1.经分析最后决定,应限制一次查询返回的最大结果数量不应该超出1万,对于一次返回结果大于限制的时候应该抛出异常,而不

    2023年04月19日
    浏览(78)
  • Spring Boot入门(14):使用Mybatis-Plus轻松实现高效自定义SQL操作!

            在上几期,我们既讲了如何整合Mybatis-Plus进行数据库的增删改查,也讲解了如何使用MP的 Wrapper 构造器,但若是遇到复杂业务逻辑,如多表联查、动态拼接条件等,这些操作往往会让代码变得冗长且难以维护。但是,有了Mybatis-Plus这个优秀的框架,我们可以轻松实现

    2024年02月10日
    浏览(41)
  • 第10讲:深入剖析 Agent 插件原理,无侵入性埋点

    AbstractClassEnhancePluginDefine 核心实现 在开始之前,先简单回顾上一课时中关于 AbstractClassEnhancePluginDefine 的一个核心知识点:AbstractClassEnhancePluginDefine 是所有插件的父类,SkywalkingAgent.Transformer 会通过其 enhanceClass() 方法返回的 ClassMatch 对象,匹配到要增强的目标类。在不同的插件

    2024年02月08日
    浏览(55)
  • 从公共业务提取来看架构演进——帐号权限篇

    在产品业务多元化的过程中,往往会遇到一些与业务发展不匹配的架构问题,这些问题可能会在业务迭代中以性能差、重复开发等形式表现出来,进而给系统的扩展和维护带来一些挑战。 本文准备以一个帐号权限的业务为例,来讨论下:当遇到架构问题时,如何识别并及时调

    2024年02月05日
    浏览(34)
  • 从公共业务提取来看架构演进——功能设置篇

    上一篇文章我们以帐号权限的提取为例,介绍了 当架构跟不上业务发展时 及时调整架构的一种思路。这篇文章我们以功能设置为例,进一步讨论公共业务提取这个话题。 功能设置在本文中是指产品开放给企业和用户的一些功能设置项,以视频会议产品为例示意如下: 上面示

    2024年02月05日
    浏览(38)
  • [虚幻引擎插件介绍] DTGlobalEvent 蓝图全局事件, Actor, UMG 相互回调,自由回调通知事件函数,支持自定义参数。

    本插件可以在虚幻的蓝图 Actor, Obiect,UMG 里面指定绑定和执行消息,可带自定义参数。 参数支持 Bool,Byte,Int,Int64,Float,Name,String,Text,Vector,Rotator,Transform,Object,Actor。 绑定事件 Bind Global Event (XXX) 此类型节点可以绑定全局回调事件。 Key :绑定的事件关键值,只有

    2024年02月09日
    浏览(59)
  • mybatis使用多参数查询

    repository接口写法: Mapper层配置文件写法: 注意:resultmap是结果集,select为查询语句。其中,#{…}中的名字要和@Param注解中的参数名对应

    2024年02月09日
    浏览(50)
  • SpringBoot(整合MyBatis + MyBatis-Plus + MyBatisX插件使用)

    1.需求分析 2.数据库表设计 3.数据库环境配置 1.新建maven项目 2.pom.xml 引入依赖 3.application.yml 配置数据源 数据库名 用户名 密码 驱动是mysql8的(因为上面使用了版本仲裁) 4.Application.java 编写启动类 5.测试 6.配置类切换druid数据源 7.测试数据源是否成功切换 4.Mybatis基础配置 1

    2024年03月20日
    浏览(54)
  • 使用 NutUI 搭建「自定义业务风格」的组件库 | 京东云技术团队

    作者:京东零售 佟恩 本文介绍,如何使用 NutUI 组件库,搭建一套为专属业务风格的业务组件库。 NutUI 是一款京东风格的移动端组件库。NutUI 目前支持 Vue 和 React技术栈,支持Taro多端适配。 一般组件库,都会给用户提供修改主题的方式。比如在 NutUI 组件库中,给用户提供了

    2024年02月03日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包