From d4ebaa3bca6fe6025b295d1938e9a9f7fc0a0279 Mon Sep 17 00:00:00 2001 From: CorvusYe Date: Tue, 15 Aug 2023 10:53:57 +0800 Subject: [PATCH 1/2] fix: when DAO/Mapper method has `Page` type param with `@Param`, the param name can not be use. --- CHANGELOG.md | 3 + .../ngbatis/io/MapperResourceLoader.java | 72 +++++++++++++++---- .../contrib/ngbatis/models/MethodModel.java | 15 ++++ 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f8fcf6e..9b0d7e35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ This source code is licensed under Apache 2.0 License. - fix: [#194](https://github.com/nebula-contrib/ngbatis/issues/194) we can name the interface by `@Component` and `@Resource`, for example: - `@Component("namedMapper")`: use `@Resource("namedMapper$Proxy")` to inject. (since v1.0) - `@Resource("namedComponent")`: use `@Resource("namedComponent")` to inject. (new feature) +- fix: when DAO/Mapper method has `Page` type param with `@Param`, the param name can not be use. + > 如原来项目中分页相关接口,用了不起作用的 `@Param`, 但 xml 还是使用 p0, p1... + > 需要将 `@Param` 移除,或者将 xml 中的参数名改成 注解的参数名,以保证参数名统一 ## Develop behavior change. - Remove deprecated classes and methods: diff --git a/src/main/java/org/nebula/contrib/ngbatis/io/MapperResourceLoader.java b/src/main/java/org/nebula/contrib/ngbatis/io/MapperResourceLoader.java index a03f644e..927c3439 100644 --- a/src/main/java/org/nebula/contrib/ngbatis/io/MapperResourceLoader.java +++ b/src/main/java/org/nebula/contrib/ngbatis/io/MapperResourceLoader.java @@ -5,12 +5,14 @@ // This source code is licensed under Apache 2.0 License. import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.nebula.contrib.ngbatis.SessionDispatcher.addSpaceToSessionPool; import static org.nebula.contrib.ngbatis.models.ClassModel.PROXY_SUFFIX; import static org.nebula.contrib.ngbatis.utils.ReflectUtil.NEED_SEALING_TYPES; import static org.nebula.contrib.ngbatis.utils.ReflectUtil.getNameUniqueMethod; import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; @@ -39,6 +41,7 @@ import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.data.repository.query.Param; import org.springframework.util.Assert; @@ -78,7 +81,7 @@ public Map load() { for (Resource resource : resources) { resultClassModel.putAll(parseClassModel(resource)); } - } catch (IOException e) { + } catch (IOException | NoSuchMethodException e) { throw new ResourceLoadException(e); } return resultClassModel; @@ -91,7 +94,8 @@ public Map load() { * @return 单个 XXXDao 的全限定名 与 当前接口所对应 XXXDao.xml 解析后的全部信息 * @throws IOException 读取xml时产生的io异常 */ - public Map parseClassModel(Resource resource) throws IOException { + public Map parseClassModel(Resource resource) + throws IOException, NoSuchMethodException { Map result = new HashMap<>(); // 从资源中获取文件信息,IO 读取 Document doc = Jsoup.parse(resource.getInputStream(), "UTF-8", "http://example.com/"); @@ -152,7 +156,8 @@ private void setClassModelBySpaceAnnotation(ClassModel cm) { * @param nodes XXXDao.xml 中 <mapper> 下的子标签。即方法标签。 * @return 返回当前XXXDao类的所有方法信息Map,k: 方法名,v:方法模型(即 xml 里一个方法标签的全部信息) */ - private Map parseMethodModel(Class namespace, List nodes) { + private Map parseMethodModel(Class namespace, List nodes) + throws NoSuchMethodException { Map methods = new HashMap<>(); List methodNames = getMethodNames(nodes); for (Node methodNode : nodes) { @@ -164,7 +169,7 @@ private Map parseMethodModel(Class namespace, List no Assert.notNull(method, "接口 " + namespace.getName() + " 中,未声明 xml 中的出现的方法:" + methodModel.getId()); checkReturnType(method, namespace); - pageSupport(method, methodModel, methodNames, methods); + pageSupport(method, methodModel, methodNames, methods, namespace); methods.put(methodModel.getId(), methodModel); } } @@ -212,16 +217,21 @@ private void checkReturnType(Method method, Class namespace) { * @param methods 用于将需要分页的接口,自动追加两个接口,用于生成动态代理 */ private void pageSupport(Method method, MethodModel methodModel, List methodNames, - Map methods) { + Map methods, Class namespace) throws NoSuchMethodException { Class[] parameterTypes = method.getParameterTypes(); List> parameterTypeList = Arrays.asList(parameterTypes); if (parameterTypeList.contains(Page.class)) { int pageParamIndex = parameterTypeList.indexOf(Page.class); MethodModel pageMethod = - createPageMethod(methodModel, methodNames, parameterTypes, pageParamIndex); + createPageMethod( + methodModel, methodNames, parameterTypes, pageParamIndex, namespace + ); methods.put(pageMethod.getId(), pageMethod); - MethodModel countMethod = createCountMethod(methodModel, methodNames, parameterTypes); + MethodModel countMethod = createCountMethod( + methodModel, methodNames, parameterTypes, namespace + ); + methods.put(countMethod.getId(), countMethod); } } @@ -232,15 +242,17 @@ private void pageSupport(Method method, MethodModel methodModel, List me * @param methodModel 分页原始方法模型 * @param methodNames 当前接口的所有方法名(用于判断自动生成的接口是否已经有同名,如果已有则不再重复创建) * @param parameterTypes 方法的全部参数类型 - * @return + * @param namespace DAO 接口类 + * @return 自动分页的计数方法 */ private MethodModel createCountMethod(MethodModel methodModel, List methodNames, - Class[] parameterTypes) { + Class[] parameterTypes, Class namespace) throws NoSuchMethodException { String methodName = methodModel.getId(); String countMethodName = String.format("%s$Count", methodName); Assert.isTrue(!methodNames.contains(countMethodName), "There is a method name conflicts with " + countMethodName); MethodModel countMethodModel = new MethodModel(); + setParamAnnotations(parameterTypes, namespace, methodName, countMethodModel); countMethodModel.setParameterTypes(parameterTypes); countMethodModel.setId(countMethodName); String cql = methodModel.getText(); @@ -261,21 +273,26 @@ private MethodModel createCountMethod(MethodModel methodModel, List meth * @param methodNames 当前接口的所有方法名(用于判断自动生成的接口是否已经有同名,如果已有则不再重复创建) * @param parameterTypes 方法的全部参数类型 * @param pageParamIndex 分页参数处在参数列表中的下标位 + * @param namespace DAO 接口类 * @return 查询范围条目方法 的方法模型 */ private MethodModel createPageMethod(MethodModel methodModel, List methodNames, - Class[] parameterTypes, int pageParamIndex) { + Class[] parameterTypes, int pageParamIndex, Class namespace) + throws NoSuchMethodException { String methodName = methodModel.getId(); String pageMethodName = String.format("%s$Page", methodName); Assert.isTrue(!methodNames.contains(pageMethodName), "There is a method name conflicts with " + pageMethodName); MethodModel pageMethodModel = new MethodModel(); + Annotation[][] parameterAnnotations = setParamAnnotations(parameterTypes, namespace, + methodName, pageMethodModel); + String pageParamName = getPageParamName(parameterAnnotations, pageParamIndex); pageMethodModel.setParameterTypes(parameterTypes); pageMethodModel.setId(pageMethodName); String cql = methodModel.getText(); - if (parameterTypes.length > 1) { - String format = "%s\t\tSKIP $p%d.startRow LIMIT $p%d.pageSize"; - cql = String.format(format, cql, pageParamIndex, pageParamIndex); + if (pageParamName != null) { + String format = "%s\t\tSKIP $%s.startRow LIMIT $%s.pageSize"; + cql = String.format(format, cql, pageParamName, pageParamName); } else { String format = "%s\t\tSKIP $startRow LIMIT $pageSize"; cql = String.format(format, cql); @@ -286,6 +303,35 @@ private MethodModel createPageMethod(MethodModel methodModel, List metho return pageMethodModel; } + private static Annotation[][] setParamAnnotations(Class[] parameterTypes, Class namespace, + String methodName, MethodModel methodModel) throws NoSuchMethodException { + Method declaredMethod = namespace.getDeclaredMethod(methodName, parameterTypes); + Annotation[][] parameterAnnotations = declaredMethod.getParameterAnnotations(); + methodModel.setParamAnnotations(parameterAnnotations); + return parameterAnnotations; + } + + private String getPageParamName(Annotation[][] parameterAnnotations, int pageParamIndex) { + if (parameterAnnotations.length > pageParamIndex) { + Annotation[] parameterAnnotation = parameterAnnotations[pageParamIndex]; + for (int i = 0; i < parameterAnnotation.length; i++) { + Annotation ifParam = parameterAnnotation[i]; + if (ifParam.annotationType() == Param.class) { + Param param = (Param) ifParam; + String paramName = param.value(); + if (isNotBlank(paramName)) { + return paramName; + } + } + } + } + // 多参数,并且没有注解时,使用 pN 的参数格式来表示参数名 + if (parameterAnnotations.length > 1) { + return "p" + pageParamIndex; + } + return null; + } + /** * 从xml标签中,获取所有的方法名。 * diff --git a/src/main/java/org/nebula/contrib/ngbatis/models/MethodModel.java b/src/main/java/org/nebula/contrib/ngbatis/models/MethodModel.java index c0ed00c7..10990578 100644 --- a/src/main/java/org/nebula/contrib/ngbatis/models/MethodModel.java +++ b/src/main/java/org/nebula/contrib/ngbatis/models/MethodModel.java @@ -56,6 +56,11 @@ public class MethodModel { */ private Class[] parameterTypes; + /** + * {@link #method} 参数列表的参数注解 + */ + private Annotation[][] paramAnnotations; + // ---------------- info in interface start --------------------- /** * 用于 asm 的方法签名。 @@ -127,6 +132,14 @@ public void setParameterTypes(Class[] parameterTypes) { this.parameterTypes = parameterTypes; } + public Annotation[][] getParamAnnotations() { + return paramAnnotations; + } + + public void setParamAnnotations(Annotation[][] paramAnnotations) { + this.paramAnnotations = paramAnnotations; + } + public int getParameterCount() { return method == null ? parameterTypes.length : method.getParameterCount(); } @@ -146,6 +159,8 @@ public void setReturnType(Class returnType) { public Annotation[][] getParameterAnnotations() { if (method != null) { return method.getParameterAnnotations(); + } else if (paramAnnotations != null) { + return paramAnnotations; } return new Annotation[getParameterCount()][]; } From 31f9349c918bc946c3b22651df7487f2664c328d Mon Sep 17 00:00:00 2001 From: CorvusYe Date: Tue, 15 Aug 2023 10:55:15 +0800 Subject: [PATCH 2/2] chore: upgrade nebula-java 3.5.0->3.6.0 --- CHANGELOG.md | 3 +++ pom.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0d7e35..edb31965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ This source code is licensed under Apache 2.0 License. - [ ] Springboot 3.x support. # NEXT +## Dependencies upgrade +- nebula-java: 3.5.0 -> 3.6.0 + ## Bugfix - fix: [#190](https://github.com/nebula-contrib/ngbatis/issues/190) Insert failed when tag has no attributes - chore: removing and exclude some packages: log4j related or useless. diff --git a/pom.xml b/pom.xml index dc06faa1..e51a77b8 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ com.vesoft client - 3.5.0 + 3.6.0