{"id":16422673,"url":"https://github.com/createsequence/mybatis-plus-join","last_synced_at":"2025-10-26T22:31:36.989Z","repository":{"id":39867697,"uuid":"459071973","full_name":"Createsequence/mybatis-plus-join","owner":"Createsequence","description":"基于myabtis-plus的连表查询扩展，支持字段别名、预设条件、group by ... having、数据库函数等扩展功能","archived":false,"fork":false,"pushed_at":"2022-09-21T03:31:13.000Z","size":893,"stargazers_count":6,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-01T00:51:03.948Z","etag":null,"topics":["java","mybatis","mybatis-plus","springboot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Createsequence.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-02-14T08:16:15.000Z","updated_at":"2024-07-09T21:39:32.000Z","dependencies_parsed_at":"2022-07-16T05:30:35.820Z","dependency_job_id":null,"html_url":"https://github.com/Createsequence/mybatis-plus-join","commit_stats":null,"previous_names":["createsequence/mybaits-plus-join"],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Createsequence%2Fmybatis-plus-join","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Createsequence%2Fmybatis-plus-join/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Createsequence%2Fmybatis-plus-join/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Createsequence%2Fmybatis-plus-join/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Createsequence","download_url":"https://codeload.github.com/Createsequence/mybatis-plus-join/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238408436,"owners_count":19467094,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","mybatis","mybatis-plus","springboot"],"created_at":"2024-10-11T07:37:21.853Z","updated_at":"2025-10-26T22:31:31.670Z","avatar_url":"https://github.com/Createsequence.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 一、简介\n\n本项目基于 mybatis-plus，提供通过条件构造器以代码方式构造 join 查询的相关功能。\n\n开发的初衷是为了解决mp日常使用中感觉到的一些痛点的，比如条件构造器不支持join语法，lambda表达式版本的group...having支持不够、查询字段与条件字段都不支持数据库函数，不支持逻辑表，像in或eq这类的方法需要重复添加判空条件......等等。\n\n本框架旨保留mp原功能的基础上，基于`Wrapper`类扩展一个新的`JoinWrapper`以在不修改已有代码的基础上支持上述功能。\n\n## 二、快速开始\n\n1. 引入 mybatis-plus-boot-starter 与 mybatis-plus-join 依赖：\n\n   ~~~xml\n   \u003cdependency\u003e\n       \u003cgroupId\u003etop.xiajibagao\u003c/groupId\u003e\n       \u003cartifactId\u003emybatis-plus-join\u003c/artifactId\u003e\n       \u003cversion\u003e${version}\u003c/version\u003e\n   \u003c/dependency\u003e\n   \n   \u003cdepeendency\u003e\n       \u003cgroupId\u003ecom.baomidou\u003c/groupId\u003e\n       \u003cartifactId\u003emybatis-plus-boot-starter\u003c/artifactId\u003e\n       \u003cversion\u003e${mybatis-plus.version}\u003c/version\u003e\n   \u003c/depeendency\u003e\n   ~~~\n\n   \u003e 本项目需要自行引入依赖 `mybatis-plus-boot-starter`，此外其他依赖皆不向下传递\n\n2. 将动态返回值插件`DynamicResultInterceptor`注册到 `com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean` 中 ，\n\n2. 然后将扩展 SQL 注入器 `JoinMethodInjector`注入到 `com.baomidou.mybatisplus.core.config.GlobalConfig`中。\n\n   这里给出一个最简单配置：\n\n   ~~~java\n   @Bean\n   public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {\n       MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();\n       sqlSessionFactory.setDataSource(dataSource);\n   \n       // 注册动态返回值插件\n       sqlSessionFactory.setPlugins(new DynamicResultInterceptor());\n   \n       // 注册扩展sql注入器\n       MybatisConfiguration configuration = new MybatisConfiguration();\n       GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);\n       globalConfig.setSqlInjector(new JoinMethodInjector());\n       sqlSessionFactory.setConfiguration(configuration);\n   \n       return sqlSessionFactory.getObject();\n   }\n   ~~~\n\n3. 令 `mapper`接口从继承 mp 提供的 `BaseMapper`换为 `JoinMapper`：\n\n   ~~~java\n   @Mapper\n   public interface FooMapper\u003cT\u003e extend JoinMapper\u003cT\u003e {\n       // ... ...\n   }\n   ~~~\n\n\n\n## 三、核心功能\n\n分别创建学生表 student，课程表 course 与考试分数表 score 三张表，其对应数据库脚本如下：\n\n~~~sql\n-- 课程表\nCREATE TABLE `course`  (\n    `id` int(0) NOT NULL AUTO_INCREMENT,\n    `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,\n    `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,\n    PRIMARY KEY (`id`) USING BTREE\n)\n\n-- 考试分数表\nCREATE TABLE `score`  (\n    `id` int(0) NOT NULL AUTO_INCREMENT,\n    `student_id` int(0) NULL DEFAULT NULL,\n    `course_id` int(0) NULL DEFAULT NULL,\n    `score` int(0) NULL DEFAULT NULL,\n    PRIMARY KEY (`id`) USING BTREE\n)\n\n-- 学生表\nCREATE TABLE `student`  (\n    `id` int(0) NOT NULL AUTO_INCREMENT,\n    `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,\n    PRIMARY KEY (`id`) USING BTREE\n)\n~~~\n\n以下示例皆基于上述三表及对应实体。\n\n### 1、字段别名\n\n条件构造器可以指定主表与可以指定对象类型，需要指定返回值对象类型，并指定别名：\n\n```java\n// 查询学生id，学生名称并指定别名，其余与默认字段相同\nJoinWrapper\u003cStudentDO, StudentDTO\u003e wrapper = JoinWrapper.create(StudentDO.class, StudentDTO.class)\n    .select(StudentDO::getName, StudentDTO::getStudentName)\n    .select(StudentDO::getId, StudentDTO::getStudentId)\n    .selectAll();\nList\u003cStudentDTO\u003e studentDTOS = studentMapper.selectListJoin(wrapper);\n```\n\n该条件构造器构造的 SQL 等同：\n\n~~~sql\nSELECT t1.*, t1.name AS student_name, t1.id AS student_id FROM student t1\n~~~\n\n### 2、扩展条件\n\n`JoinWrapper`基于 mp 的条件构造器原有方法额外提供三个方向的扩展：\n\n- 基于 Lambda 表达式的应用条件；\n- 预设的应用条件：包括 `in/notInIfNotEmpty`，`eqIfNotNull`，`likeIfNotBank`，`between/notBetweenIfAllNotNull`等；\n- 补充方法：包括 `notLikeRight`，`notLikeLeft`，`limit`等；\n\n如：\n\n~~~java\nJoinWrapper\u003cStudentDO, StudentDTO\u003e wrapper = JoinWrapper.create(StudentDO.class, StudentDTO.class)\n    // 基于lambda表达式的应用条件\n    .eq(Objects::nonNull, StudentDO::getName, null)\n    .in(t -\u003e !t.isEmpty(), StudentDO::getId, Arrays.asList(1, 2, 3))\n    // 预设应用条件\n    .eqIfNotNull(StudentDO::getName, null)\n    .inIfNotEmpty(StuedntDO::getId, Collections.emptyList())\n    // 补充方法\n    .notLikeRight(CharSequenceUtil::isNotBlank, StudentDO::getName, \"小明\")\n    .limit(true, 1);\n\nList\u003cStudentDTO\u003e studentDTOS = studentMapper.selectListJoin(wrapper);\n~~~\n\n最终构造的 SQL同：\n\n~~~sql\nSELECT t1.* FROM student t1 WHERE (t1.name NOT LIKE '小明%' and t1.id in (1, 2, 3)) LIMIT 1\n~~~\n\n### 3、连表查询\n\n`JoinWrapper`支持构造关联查询：\n\n~~~java\n// 查询学生成绩\nJoinWrapper\u003cStudentDO, StudentDTO\u003e wrapper = JoinWrapper.create(StudentDO.class, StudentDTO.class)\n    .selectAll()\n    .leftJoin(ScoreDO.class, w -\u003e w\n\t\t.on(StudentDO::getId, Condition.EQ, ScoreDO::getStudentId)\n\t\t.select(ScoreDO::getScore, StudentDTO::getScore)\n\t\t.leftJoin(CourseDO.class)\n\t\t.on(ScoreDO::getCourseId, Condition.EQ, CourseDO::getId)\n\t\t.select(CourseDO::getName, StudentDTO::getCourseName)\n     );\n\n// 该写法等同于\nJoinWrapper\u003cStudentDO, StudentDTO\u003e wrapper = JoinWrapper.create(StudentDO.class, StudentDTO.class)\n\t.selectAll()\n\t.leftJoin(ScoreDO.class, w -\u003e w\n\t\t.on(StudentDO::getId, Condition.EQ, ScoreDO::getStudentId)\n\t\t.select(ScoreDO::getScore, StudentDTO::getScore)\n\t\t.leftJoin(CourseDO.class, w2 -\u003e w2\n\t\t\t.on(ScoreDO::getCourseId, Condition.EQ, CourseDO::getId)\n\t\t\t.select(CourseDO::getName, StudentDTO::getCourseName)\n\t\t)\n\t);\n~~~\n\n该条件构造器构造的 SQL 等同：\n\n~~~sql\nSELECT t1.*, t2.score AS score, t3.name AS course_name \nFROM student t1 \nLEFT JOIN score t2 ON (t1.id = t2.student_id) \nLEFT JOIN course t3 ON (t2.course_id = t3.id)\n~~~\n\n支持的关联查询包括 `fulljoin`、`left join`、`right join`、`inner join` 四种，关联的每张表都可以添加复数的普通条件、关联条件(on)以及查询字段，比如：\n\n~~~java\nJoinWrapper\u003cStudentDO, StudentDTO\u003e wrapper = JoinWrapper.create(StudentDO.class, StudentDTO.class)\n    .selectAll()\n    .eqIfNotNull(StudentDO::getName, \"小明\")\n    .leftJoin(ScoreDO.class, w -\u003e w\n\t\t.on(StudentDO::getId, Condition.EQ, ScoreDO::getStudentId)\n\t\t.select(ScoreDO::getScore, StudentDTO::getScore)\n\t\t.le(ScoreDO::getScore, 60)\n\t\t.leftJoin(CourseDO.class, w2 -\u003e w2\n\t\t\t.on(ScoreDO::getCourseId, Condition.EQ, CourseDO::getId)\n\t\t\t.select(CourseDO::getName, StudentDTO::getCourseName)\n\t\t\t.likeIfNotBank(CourseDO::getType, \"文科\")\n\t\t)\n\t);\n~~~\n\n该条件构造器构造的 SQL 如下：\n\n~~~sql\n SELECT t1.*, t2.score AS score, t3.name AS course_name\n FROM student t1\n LEFT JOIN score t2\n ON (t1.id = t2.student_id)\n LEFT JOIN course t3\n ON (t2.course_id = t3.id)\n WHERE (t1.name = '小明' AND t2.score \u003c= 60 AND t3.type LIKE '%文科%');\n~~~\n\n### 4、数据库函数字段\n\n`JoinWrapper`支持将数据库函数作为字段，可以有三种用法：\n\n- 作为查询字段，如：`select ifNull(a.name, 'fack name')`；\n- 作为查询条件，包括 where 与 having 条件；\n- 用于函数嵌套，如 `concat('user: ', ifNull(a.name, 'fack name'))`；\n\n由于在关联查询时必须指定表字段来源表的别名，因此创建表字段需要通过 `JoinWrapper.toTableColumn()`将字段与表进行绑定，然后可通过函数字段工厂类`top.xiajibagao.mybatis.plus.join.wrapper.column.Columns`对获取的字段进行函数化。\n\n支持的函数：\n\n- 日期类：now, currentTimestamp, currentDate, currentTime, dateFormat, day, month, year;\n- 数学：abs, avg, max, min, sum, rand, count;\n- 字符串：ifNull, concat, format, replace, upper, lower;\n- 控制流：case..then...when...else;\n\n\u003e **注意**：部分函数可能不受某些数据库支持，请根据自己项目使用的数据库选择性使用\n\n#### Select\n\n如：\n\n```java\n// 查询分数，并根据分段给出评价\nJoinWrapper\u003cScoreDO, StudentDTO\u003e wrapper = JoinWrapper.create(ScoreDO.class, StudentDTO.class);\nwrapper.select(ScoreDO::getScore)\n    .caseByCondition(StudentDTO::getRemark)\n        .when(wrapper.toTableColumn(ScoreDO::getScore), Condition.GE, 90, \"'优'\")\n        .when(wrapper.toTableColumn(ScoreDO::getScore), Condition.GE, 60, \"'及格'\")\n        .el(() -\u003e \"'不及格'\")\n    .end();\n```\n\n该条件构造器构造的 SQL 同：\n\n~~~sql\nSELECT t1.score, (\n    CASE t1.score \n    WHEN t1.score \u003e= 90 THEN '优' \n    WHEN t1.score \u003e= 60 THEN '及格' \n    ELSE '不及格' END\n) AS remark \nFROM score t1\n~~~\n\n#### Where\n\n像函数这类特殊的字段需要依靠`where()`方法构建：\n\n~~~java\nJoinWrapper\u003cScoreDO, StudentDTO\u003e wrapper = JoinWrapper.create(ScoreDO.class, StudentDTO.class);\nwrapper.selectAll()\n    .where(Columns.plus(wrapper.toTableColumn(ScoreDO::getScore), 5), Condition.EQ, 100);\n~~~\n\n构建的 SQL 同：\n\n~~~sql\nSELECT t1.* FROM score t1 WHERE ((t1.score + 5) = 100)\n~~~\n\n#### Having\n\nHaving 关键字需要配合 group by 使用：\n\n~~~java\n// 查询挂了不止1人的科目的挂科人数\nJoinWrapper\u003cScoreDO, StudentDTO\u003e wrapper = JoinWrapper.create(ScoreDO.class, StudentDTO.class);\nwrapper.select(ScoreDO::getCourseId, StudentDTO::getCourseId)\n    .select(Columns.count(), StudentDTO::getNum)\n    .where(ScoreDO::getScore, Condition.LT, 60)\n    .groupBy(ScoreDO::getCourseId)\n    .having(Columns.count(), Condition.GT, 1);\n~~~\n\n构建的 SQL 同：\n\n~~~sql\nSELECT t1.course_id AS course_id, COUNT(*) AS num \nFROM score t1 \nWHERE (t1.score \u003c 60) \nGROUP BY t1.course_id \nHAVING COUNT(*) \u003e 1\n~~~\n\n### 5、子查询\n\nJoinWrapper 允许将一个已经构造好的条件构造器转为一张逻辑表/临时表，并用于子查询。\n\n#### JOIN\n\n~~~java\n// 查询挂科超过1人的科目的挂科人数\nJoinWrapper\u003cScoreDO, StudentDTO\u003e logicTable = JoinWrapper.create(ScoreDO.class, StudentDTO.class);\nlogicTable.select(ScoreDO::getCourseId, StudentDTO::getCourseId)\n    .select(Columns.count(), StudentDTO::getNum)\n    .where(ScoreDO::getScore, Condition.LT, 60)\n    .groupBy(ScoreDO::getCourseId)\n    .having(Columns.count(), Condition.GT, \"1\");\n\n// 查询挂科超过1人的科目的科目信息与挂科人数\nJoinWrapper\u003cCourseDO, StudentDTO\u003e wrapper = JoinWrapper.create(CourseDO.class, StudentDTO.class);\nwrapper.selectAll()\n    // 关联逻辑表\n    .innerJoin(logicTable)\n    .on(CourseDO::getId, Condition.EQ, StudentDTO::getCourseId)\n    .selectAll();\n~~~\n\n该条件构造器构造的 SQL 同：\n\n~~~sql\n SELECT t1.*, t2.*\n FROM course t1\n INNER JOIN (\n     SELECT t1.course_id AS course_id, COUNT(*) AS num\n     FROM score t1\n     WHERE (t1.score \u003c 60) GROUP BY t1.course_id HAVING COUNT(*) \u003e 1\n ) t2 ON (t1.id = t2.course_id);\n~~~\n\n#### FROM\n\n~~~java\n// 查询挂科超过1人的科目及挂科人数\nJoinWrapper\u003cScoreDO, StudentDTO\u003e wrapper = JoinWrapper.create(ScoreDO.class, StudentDTO.class);\nwrapper.select(ScoreDO::getCourseId, StudentDTO::getCourseId)\n    .select(Columns.count(), StudentDTO::getNum)\n    .where(ScoreDO::getScore, Condition.LT, 60)\n    .groupBy(ScoreDO::getCourseId)\n    .having(Columns.count(), Condition.GT, 1);\n\n// 将上一查询转为逻辑表，然后查询该科目名称\nJoinWrapper\u003cStudentDTO, StudentDTO\u003e logicTable = wrapper.toLogicTable()\n    .selectAll()\n    .leftJoin(CourseDO.class, w -\u003e w\n\t\t.on(StudentDTO::getCourseId, Condition.EQ, CourseDO::getId)\n\t\t.select(CourseDO::getName, StudentDTO::getCourseName)\n\t);\n~~~\n\n该条件构造器构造的 SQL 同：\n\n~~~sql\nSELECT t1.*, t2.name AS course_name \nFROM (\n    SELECT t1.course_id AS course_id, COUNT(*) AS num \n    FROM score t1 \n    WHERE (t1.score \u003c 60) \n    GROUP BY t1.course_id \n    HAVING COUNT(*) \u003e 1\n) t1 \nLEFT JOIN course t2 ON (t1.course_id = t2.id)\n~~~\n\n#### WHERE\n\n~~~java\n// 查询挂科人数超过1人的科目\nJoinWrapper\u003cCourseDO, StudentDTO\u003e wrapper = JoinWrapper.create(CourseDO.class, StudentDTO.class);\nwrapper.selectAll()\n    .where(wrapper.toTableColumn(CourseDO::getId), Condition.IN, Columns.subQuery(\n        JoinWrapper.create(ScoreDO.class, StudentDTO.class)\n            .select(ScoreDO::getCourseId, StudentDTO::getCourseId)\n            .where(ScoreDO::getScore, Condition.LT, 60)\n            .groupBy(ScoreDO::getCourseId)\n            .having(Columns.count(), Condition.GT, \"1\")\n    ));\n~~~\n\n该条件构造器构造的 SQL 同：\n\n~~~sql\nSELECT t1.*\nFROM course t1\nWHERE (\n    t1.id IN (\n        SELECT t1.course_id AS course_id\n        FROM score t1\n        WHERE (t1.score \u003c 60) GROUP BY t1.course_id HAVING COUNT(*) \u003e 1\n    )\n);\n~~~\n\n\n\n### 6、原生方法适配\n\n#### 兼容BaseMapper方法\n\nJoinWrapper 兼容 mp 原生 Wrapper 中**除`setEntity()`外**的全部查询方法，并且也可以直接作为参数传入 BaseMapper 的方法中：\n\n~~~java\n// BaseMapper.selectList(Wrapper\u003cT\u003e wrapper)\nJoinWrapper\u003cStudentDO, StudentDTO\u003e wrapper = JoinWrapper.create(StudentDO.class, StudentDTO.class);\nList\u003cStudentDO\u003e students = studentMapper.selectList(wrapper);\n~~~\n\n不过这样使用时，除 Join 条件将不生效外，其余扩展功能仍可以正常使用。\n\n#### 逻辑删除\n\n当配置了逻辑删除时（具体配置参见[mybaits plus逻辑删除](https://baomidou.com/pages/6b03c5/)），JoinWrapper 将在初始化时，自动逻辑删除字段作为查询条件添加到 Where 条件后，关联表亦同。\n\n假设现在已有配置：\n\n~~~yml\nmybatis-plus:\n global-config:\n  db-config:\n   logic-delete-field: isDelete\n   logic-delete-value: 1\n   logic-not-delete-value: 0\n~~~\n\n当我们使用条件构造成构建一个查询时，会自动从 `com.baomidou.mybatisplus.core.metadata.TableInfo`获取逻辑删除相关配置，并自动添加条件 `logic-delete-field = login-not-delete-value`，如：\n\n~~~java\nJoinWrapper\u003cStudentDO, StudentDTO\u003e wrapper = JoinWrapper.create(StudentDO.class, StudentDTO.class);\n        List\u003cStudentDO\u003e students = studentMapper.selectList(wrapper);\n~~~\n\n若 `StudentDO`及对于表存在字段`is_delete`，且已有相关逻辑删除配置，则实际构造出的 SQL 为：\n\n~~~sql\nSELECT * FROM student t1 where t1.is_delete = 0\n~~~\n\n#### 分页\n\n参见[mybtis-plus分页插件](https://baomidou.com/pages/97710a/#paginationinnerinterceptor)，该插件基于 SQL 分析生效，因此不受影响。\n\n但是要注意，与当使用`JoinWrapper`构建关联查询时，与原写法一样，若 join 的表没有 where 条件，则生成的 countSql 会忽略 join 部分的表导致查询数据行数与实际待分页数据行数不一致。 ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcreatesequence%2Fmybatis-plus-join","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcreatesequence%2Fmybatis-plus-join","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcreatesequence%2Fmybatis-plus-join/lists"}