From 98deb27d93a6b598ec7ddc57e781032839dbfebf Mon Sep 17 00:00:00 2001 From: xhliu <1900327840@qq.com> Date: Fri, 21 Apr 2023 15:55:34 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=9C=89=E5=85=B3=E5=BC=80=E7=AA=97?= =?UTF-8?q?=E5=87=BD=E6=95=B0=20ROW=5FNUMBER=20=E7=9A=84=E9=80=9A=E7=94=A8?= =?UTF-8?q?=20Mapper=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/mapper/common/ExampleMapper.java | 1 + .../SelectRowNumberByExampleMapper.java | 24 +++++ .../mapper/provider/ExampleProvider.java | 10 ++ .../tk/mybatis/mapper/entity/Example.java | 19 ++++ .../mapper/entity/RowNumberExample.java | 98 +++++++++++++++++++ .../mapper/mapperhelper/SqlHelper.java | 28 +++++- .../java/tk/mybatis/mapper/util/OGNL.java | 72 ++++++++++++++ 7 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 base/src/main/java/tk/mybatis/mapper/common/example/SelectRowNumberByExampleMapper.java create mode 100644 core/src/main/java/tk/mybatis/mapper/entity/RowNumberExample.java diff --git a/base/src/main/java/tk/mybatis/mapper/common/ExampleMapper.java b/base/src/main/java/tk/mybatis/mapper/common/ExampleMapper.java index 9dee4a987..8df266c20 100644 --- a/base/src/main/java/tk/mybatis/mapper/common/ExampleMapper.java +++ b/base/src/main/java/tk/mybatis/mapper/common/ExampleMapper.java @@ -35,6 +35,7 @@ @tk.mybatis.mapper.annotation.RegisterMapper public interface ExampleMapper extends SelectByExampleMapper, + SelectRowNumberByExampleMapper, SelectOneByExampleMapper, SelectCountByExampleMapper, DeleteByExampleMapper, diff --git a/base/src/main/java/tk/mybatis/mapper/common/example/SelectRowNumberByExampleMapper.java b/base/src/main/java/tk/mybatis/mapper/common/example/SelectRowNumberByExampleMapper.java new file mode 100644 index 000000000..0bd6743ea --- /dev/null +++ b/base/src/main/java/tk/mybatis/mapper/common/example/SelectRowNumberByExampleMapper.java @@ -0,0 +1,24 @@ +package tk.mybatis.mapper.common.example; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.SelectProvider; +import tk.mybatis.mapper.annotation.RegisterMapper; +import tk.mybatis.mapper.entity.Example; +import tk.mybatis.mapper.provider.ExampleProvider; + +import java.util.List; + + +/** + * 通过相关的 {@link Example} 查询对象,查询结果集中按照指定列进行分区排序并且指定排名的所有结果对象, + * 这个查询相当于 SQL 中的开窗函数 ROW_NUMBER 查询 + * + * @author xhliu + * @param 相关的实体类数据类型 + */ +@RegisterMapper +public interface SelectRowNumberByExampleMapper { + + @SelectProvider(type = ExampleProvider.class, method = "dynamicSQL") + List selectRowNumberByExample(Example example); +} diff --git a/base/src/main/java/tk/mybatis/mapper/provider/ExampleProvider.java b/base/src/main/java/tk/mybatis/mapper/provider/ExampleProvider.java index dcd4dbca8..d4afbd1f4 100644 --- a/base/src/main/java/tk/mybatis/mapper/provider/ExampleProvider.java +++ b/base/src/main/java/tk/mybatis/mapper/provider/ExampleProvider.java @@ -178,4 +178,14 @@ public String updateByExample(MappedStatement ms) { public String selectOneByExample(MappedStatement ms) { return selectByExample(ms); } + + /** + * 根据 ROW_NUMBER 开窗函数的相关参数执行对应的查询 + */ + public String selectRowNumberByExample(MappedStatement ms) { + Class clazz = getEntityClass(ms); + return SqlHelper.rowNumberBefore() + + selectByExample(ms) + + SqlHelper.rowNumberAfter(clazz); + } } diff --git a/core/src/main/java/tk/mybatis/mapper/entity/Example.java b/core/src/main/java/tk/mybatis/mapper/entity/Example.java index c9c635fc8..370b29076 100644 --- a/core/src/main/java/tk/mybatis/mapper/entity/Example.java +++ b/core/src/main/java/tk/mybatis/mapper/entity/Example.java @@ -69,6 +69,8 @@ public class Example implements IDynamicTableName { protected OrderBy ORDERBY; + protected RowNumberExample rowNumberParam; // ROW_NUMBER 函数需要的相关参数 + /** * 默认exists为true * @@ -104,6 +106,7 @@ public Example(Class entityClass, boolean exists, boolean notNull) { //根据李领北建议修改#159 propertyMap = table.getPropertyMap(); this.ORDERBY = new OrderBy(this, propertyMap); + this.rowNumberParam = RowNumberExample.EMPTY; } @@ -119,6 +122,7 @@ private Example(Builder builder) { this.forUpdate = builder.forUpdate; this.tableName = builder.tableName; this.ORDERBY = new OrderBy(this, propertyMap); + this.rowNumberParam = builder.rowNumberParam; if (!StringUtil.isEmpty(builder.orderByClause.toString())) { this.orderByClause = builder.orderByClause.toString(); @@ -226,6 +230,10 @@ public Map getPropertyMap() { return propertyMap; } + public RowNumberExample getRowNumberParam() { + return rowNumberParam; + } + public static class OrderBy { //属性和列对应 protected Map propertyMap; @@ -844,6 +852,8 @@ public static class Builder { //动态表名 private String tableName; + private RowNumberExample rowNumberParam; + public Builder(Class entityClass) { this(entityClass, true); } @@ -892,6 +902,11 @@ public Builder select(String... properties) { return this; } + public Builder rowNumberParam(RowNumberExample param) { + this.rowNumberParam = param; + return this; + } + public Builder notSelect(String... properties) { if (properties != null && properties.length > 0) { if (this.excludeColumns == null) { @@ -1148,4 +1163,8 @@ public void setCountProperty(String property) { public void setTableName(String tableName) { this.tableName = tableName; } + + public void setRowNumberParam(RowNumberExample rowNumberParam) { + this.rowNumberParam = rowNumberParam; + } } \ No newline at end of file diff --git a/core/src/main/java/tk/mybatis/mapper/entity/RowNumberExample.java b/core/src/main/java/tk/mybatis/mapper/entity/RowNumberExample.java new file mode 100644 index 000000000..e22336dca --- /dev/null +++ b/core/src/main/java/tk/mybatis/mapper/entity/RowNumberExample.java @@ -0,0 +1,98 @@ +package tk.mybatis.mapper.entity; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用于开窗函数 ROW_NUMBER 查询的查询参数对象 + * + * @author lxh + */ +public class RowNumberExample { + + private static final boolean ASC = true; + + public final static RowNumberExample EMPTY = null; + + // 用于开窗函数 RowNumber 查询的分区列 + private final List partCols; + + // 用于开窗函数 RowNumber 查询的排序列 + private final List sortCols; + + // 用于开窗函数 RowNUmber 的排序方向(正序或逆序) + private final boolean sortOriented; + + // 每组需要查询的排序编号,默认为 1 + private final int rank; + + public RowNumberExample(List partCols, + List sortCols, + boolean sortOriented, + int rank) { + this.partCols = new ArrayList(partCols); + this.sortCols = new ArrayList(sortCols); + this.sortOriented = sortOriented; + this.rank = rank; + } + + public List getPartCols() { + return partCols; + } + + public List getSortCols() { + return sortCols; + } + + public boolean isSortOriented() { + return sortOriented; + } + + public int getRank() { + return rank; + } + + public static final class Builder { + private final List partCols = new ArrayList(); + private final List sortCols = new ArrayList(); + private boolean sortOriented = ASC; + private int rank = 1; + + private Builder() { + } + + public static Builder builder() { + return new Builder(); + } + + public Builder partCols(List partCols) { + if (partCols == null || partCols.isEmpty()) { + return this; + } + this.partCols.addAll(partCols); + return this; + } + + public Builder sortCols(List sortCols) { + if (sortCols == null || sortCols.isEmpty()) { + return this; + } + this.sortCols.addAll(sortCols); + return this; + } + + public Builder sortOriented(boolean sortOriented) { + this.sortOriented = sortOriented; + return this; + } + + public Builder rank(int rank) { + this.rank = rank; + return this; + } + + public RowNumberExample build() { + return new RowNumberExample(partCols, sortCols, sortOriented, rank); + } + } +} diff --git a/core/src/main/java/tk/mybatis/mapper/mapperhelper/SqlHelper.java b/core/src/main/java/tk/mybatis/mapper/mapperhelper/SqlHelper.java index 949a44042..2dc311c2f 100644 --- a/core/src/main/java/tk/mybatis/mapper/mapperhelper/SqlHelper.java +++ b/core/src/main/java/tk/mybatis/mapper/mapperhelper/SqlHelper.java @@ -28,10 +28,13 @@ import tk.mybatis.mapper.annotation.LogicDelete; import tk.mybatis.mapper.annotation.Version; import tk.mybatis.mapper.entity.EntityColumn; +import tk.mybatis.mapper.entity.Example; import tk.mybatis.mapper.entity.IDynamicTableName; +import tk.mybatis.mapper.entity.RowNumberExample; import tk.mybatis.mapper.util.StringUtil; import tk.mybatis.mapper.version.VersionException; +import java.util.List; import java.util.Set; /** @@ -698,18 +701,18 @@ public static String whereAllIfColumns(Class entityClass, boolean empty, bool * @param entityClass * @return */ - public static String whereVersion(Class entityClass){ - return whereVersion(entityClass,null); + public static String whereVersion(Class entityClass) { + return whereVersion(entityClass, null); } /** * 乐观锁字段条件 * * @param entityClass - * @param entityName 实体名称 + * @param entityName 实体名称 * @return */ - public static String whereVersion(Class entityClass,String entityName) { + public static String whereVersion(Class entityClass, String entityName) { Set columnSet = EntityHelper.getColumns(entityClass); boolean hasVersion = false; String result = ""; @@ -1030,4 +1033,21 @@ public static String updateByExampleWhereClause() { ""; } + public static String rowNumberBefore() { + return "SELECT * FROM (" + + "\n" + + "\tSELECT *,\n" + + "\t\t${@tk.mybatis.mapper.util.OGNL@rowNumberSql(_parameter)} AS mapper_rank_x\n" + + "\tFROM (\n" + + "\n"; + } + + public static String rowNumberAfter(Class entityClass) { + return "\n" + + "\t) AS tmp\n" + + "\n" + + ") AS tmp\n" + + "WHERE mapper_rank_x=${@tk.mybatis.mapper.util.OGNL@rowNumberCondition(_parameter)}\n" + + exampleOrderBy(entityClass) + "\n"; + } } diff --git a/core/src/main/java/tk/mybatis/mapper/util/OGNL.java b/core/src/main/java/tk/mybatis/mapper/util/OGNL.java index a5e4a8959..3fdb1af50 100644 --- a/core/src/main/java/tk/mybatis/mapper/util/OGNL.java +++ b/core/src/main/java/tk/mybatis/mapper/util/OGNL.java @@ -29,6 +29,7 @@ import tk.mybatis.mapper.entity.EntityColumn; import tk.mybatis.mapper.entity.Example; import tk.mybatis.mapper.entity.IDynamicTableName; +import tk.mybatis.mapper.entity.RowNumberExample; import tk.mybatis.mapper.mapperhelper.EntityHelper; import tk.mybatis.mapper.mapperhelper.SqlHelper; @@ -274,4 +275,75 @@ private static boolean hasWhereCause(Example example) { return false; } + /** + * 组建 ROW_NUMBER 函数的实际调用语句 + * @throws IllegalArgumentException 不存在 ROW_NUMBER 函数参数时抛出 + */ + public static String rowNumberSql(Object arg) { + Example example = rowNumberExample(arg); + checkRowNUmberParam(example); + RowNumberExample param = example.getRowNumberParam(); + Class clazz = example.getEntityClass(); + StringBuilder sb = new StringBuilder(); + List partCols = convertToDbCols(param.getPartCols(), clazz); + List sortCols = convertToDbCols(param.getSortCols(), clazz); + sb.append("ROW_NUMBER() OVER(PARTITION BY ") + .append(join(partCols)).append(" ORDER BY ") + .append(join(sortCols)) + .append(param.isSortOriented() ? " ASC" : " DESC") + .append(") "); + return sb.toString(); + } + + private static List convertToDbCols(List fields, Class clazz) { + if (fields == null || fields.isEmpty()) { + return new ArrayList(); + } + Set cols = EntityHelper.getColumns(clazz); + if (cols == null || cols.isEmpty()) { + return new ArrayList(); + } + List ans = new ArrayList(); + Map map = new HashMap(); + for (EntityColumn col : cols) { + map.put(col.getProperty(), col.getColumn()); + } + for (String field : fields) { + ans.add(map.get(field)); + } + return ans; + } + + private static String join(List ss) { + if (ss == null || ss.isEmpty()) return ""; + if (ss.size() == 1) return ss.get(0); + int n = ss.size(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n; i++) { + sb.append(ss.get(i)); + if (i != n - 1) sb.append(", "); + } + return sb.toString(); + } + + // 过滤条件,需要转换成对应的字符串形式 + public static String rowNumberCondition(Object arg) { + Example example = rowNumberExample(arg); + checkRowNUmberParam(example); + RowNumberExample param = example.getRowNumberParam(); + return String.valueOf(param.getRank()); + } + + private static Example rowNumberExample(Object arg) { + if (!(arg instanceof Example)) { + throw new IllegalArgumentException("查询时参数类型不一致,应为 " + Example.class); + } + return (Example) arg; + } + + private static void checkRowNUmberParam(Example example) { + if (example == null || example.getRowNumberParam() == null) { + throw new IllegalArgumentException("生成 ROW_NUMBER 查询参数时参数对象不能为 null"); + } + } } From b6191f120163f09f812ef5adc1f48b3da9a91011 Mon Sep 17 00:00:00 2001 From: xhliu <1900327840@qq.com> Date: Mon, 24 Apr 2023 21:38:54 +0800 Subject: [PATCH 2/2] =?UTF-8?q?tk.mybatis.mapper.test.able.TestBasicAble#t?= =?UTF-8?q?estInsert=20=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/tk/mybatis/mapper/test/able/TestBasicAble.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/base/src/test/java/tk/mybatis/mapper/test/able/TestBasicAble.java b/base/src/test/java/tk/mybatis/mapper/test/able/TestBasicAble.java index ce403d981..61776f2f5 100644 --- a/base/src/test/java/tk/mybatis/mapper/test/able/TestBasicAble.java +++ b/base/src/test/java/tk/mybatis/mapper/test/able/TestBasicAble.java @@ -55,7 +55,14 @@ public void testInsert() { Assert.assertEquals(1, mapper.insert(userInfo)); Assert.assertNotNull(userInfo.getId()); - Assert.assertEquals(6, (int) userInfo.getId()); + + /* + 对于表名的处理,在不存在 `@Table` 的注解修饰的实体类中,会按照陀峰式的命名规则进行表明的解析, + 因此和 tk.mybatis.mapper.model.UserInfo 共用了一个表,并且由于操作系统的调度问题,对于 UserInfo + 实体的插入测试可能会发生在此单元测试之前,导致本项测试未通过的情况 + */ + // Assert.assertEquals(6, (int) userInfo.getId()); + Assert.assertTrue(userInfo.getId() >= 6); userInfo = mapper.selectByPrimaryKey(userInfo.getId()); //email没有插入