Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

有关开窗函数 ROW_NUMBER 的通用 Mapper 实现 #880

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
@tk.mybatis.mapper.annotation.RegisterMapper
public interface ExampleMapper<T> extends
SelectByExampleMapper<T>,
SelectRowNumberByExampleMapper<T>,
SelectOneByExampleMapper<T>,
SelectCountByExampleMapper<T>,
DeleteByExampleMapper<T>,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <T> 相关的实体类数据类型
*/
@RegisterMapper
public interface SelectRowNumberByExampleMapper<T> {

@SelectProvider(type = ExampleProvider.class, method = "dynamicSQL")
List<T> selectRowNumberByExample(Example example);
}
10 changes: 10 additions & 0 deletions base/src/main/java/tk/mybatis/mapper/provider/ExampleProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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没有插入
Expand Down
19 changes: 19 additions & 0 deletions core/src/main/java/tk/mybatis/mapper/entity/Example.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public class Example implements IDynamicTableName {

protected OrderBy ORDERBY;

protected RowNumberExample rowNumberParam; // ROW_NUMBER 函数需要的相关参数

/**
* 默认exists为true
*
Expand Down Expand Up @@ -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;
}


Expand All @@ -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();
Expand Down Expand Up @@ -226,6 +230,10 @@ public Map<String, EntityColumn> getPropertyMap() {
return propertyMap;
}

public RowNumberExample getRowNumberParam() {
return rowNumberParam;
}

public static class OrderBy {
//属性和列对应
protected Map<String, EntityColumn> propertyMap;
Expand Down Expand Up @@ -844,6 +852,8 @@ public static class Builder {
//动态表名
private String tableName;

private RowNumberExample rowNumberParam;

public Builder(Class<?> entityClass) {
this(entityClass, true);
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
}
98 changes: 98 additions & 0 deletions core/src/main/java/tk/mybatis/mapper/entity/RowNumberExample.java
Original file line number Diff line number Diff line change
@@ -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<String> partCols;

// 用于开窗函数 RowNumber 查询的排序列
private final List<String> sortCols;

// 用于开窗函数 RowNUmber 的排序方向(正序或逆序)
private final boolean sortOriented;

// 每组需要查询的排序编号,默认为 1
private final int rank;

public RowNumberExample(List<String> partCols,
List<String> sortCols,
boolean sortOriented,
int rank) {
this.partCols = new ArrayList<String>(partCols);
this.sortCols = new ArrayList<String>(sortCols);
this.sortOriented = sortOriented;
this.rank = rank;
}

public List<String> getPartCols() {
return partCols;
}

public List<String> getSortCols() {
return sortCols;
}

public boolean isSortOriented() {
return sortOriented;
}

public int getRank() {
return rank;
}

public static final class Builder {
private final List<String> partCols = new ArrayList<String>();
private final List<String> sortCols = new ArrayList<String>();
private boolean sortOriented = ASC;
private int rank = 1;

private Builder() {
}

public static Builder builder() {
return new Builder();
}

public Builder partCols(List<String> partCols) {
if (partCols == null || partCols.isEmpty()) {
return this;
}
this.partCols.addAll(partCols);
return this;
}

public Builder sortCols(List<String> 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);
}
}
}
28 changes: 24 additions & 4 deletions core/src/main/java/tk/mybatis/mapper/mapperhelper/SqlHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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<EntityColumn> columnSet = EntityHelper.getColumns(entityClass);
boolean hasVersion = false;
String result = "";
Expand Down Expand Up @@ -1030,4 +1033,21 @@ public static String updateByExampleWhereClause() {
"</where>";
}

public static String rowNumberBefore() {
return "SELECT * FROM ("
+ "<if test=\"_parameter != null\">\n"
+ "\tSELECT *,\n"
+ "\t\t${@tk.mybatis.mapper.util.OGNL@rowNumberSql(_parameter)} AS mapper_rank_x\n"
+ "\tFROM (\n"
+ "</if>\n";
}

public static String rowNumberAfter(Class<?> entityClass) {
return "<if test=\"_parameter != null\">\n"
+ "\t) AS tmp\n"
+ "</if>\n"
+ ") AS tmp\n"
+ "WHERE mapper_rank_x=${@tk.mybatis.mapper.util.OGNL@rowNumberCondition(_parameter)}\n"
+ exampleOrderBy(entityClass) + "\n";
}
}
72 changes: 72 additions & 0 deletions core/src/main/java/tk/mybatis/mapper/util/OGNL.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<String> partCols = convertToDbCols(param.getPartCols(), clazz);
List<String> 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<String> convertToDbCols(List<String> fields, Class<?> clazz) {
if (fields == null || fields.isEmpty()) {
return new ArrayList<String>();
}
Set<EntityColumn> cols = EntityHelper.getColumns(clazz);
if (cols == null || cols.isEmpty()) {
return new ArrayList<String>();
}
List<String> ans = new ArrayList<String>();
Map<String, String> map = new HashMap<String, String>();
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<String> 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");
}
}
}