From 7fb441fccb6eed10bce9d9d83895127c2a71acb7 Mon Sep 17 00:00:00 2001 From: CorvusYe Date: Thu, 2 May 2024 04:33:53 +0800 Subject: [PATCH] fix: when the field is aliased by `@Column`, the param name is incorrect. --- CHANGELOG.md | 14 ++- README-CN.md | 11 +-- README.md | 11 +-- ngbatis-demo/pom.xml | 2 +- .../src/main/resources/testgraph.ngql | 13 +-- .../demo/repository/ColumnAliasDaoTest.java | 87 +++++++++++++++++-- pom.xml | 2 +- .../ngbatis/binding/DefaultArgsResolver.java | 5 +- .../ngbatis/binding/beetl/functions/KvFn.java | 8 ++ .../proxy/MapperProxyClassGenerator.java | 8 +- .../contrib/ngbatis/utils/ReflectUtil.java | 13 ++- .../contrib/ngbatis/utils/ResultSetUtil.java | 23 +++-- src/main/resources/NebulaDaoBasic.xml | 72 ++++++++------- 13 files changed, 201 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7822a1ce..60fa0c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,17 +26,25 @@ This source code is licensed under Apache 2.0 License. - [x] Springboot 3.x support. (lastest-jdk17) -# NEXT (1.2.2-SNAPSHOT) +# 1.2.2 ## Bugfix - fix: complete the error code of ResultSet into QueryException. - fix: the issue of not being able to handle Set type. - fix: the issue of the ranking value of the edge object cannot be filled. +- fix: when the field is aliased by `@Column`, the param name is incorrect. (multi tags support) + - selectBySelective + - selectIdBySelective + - selectBySelectiveStringLike + - selectIdBySelectiveStringLike +- fix: unable to read the correct value of id, the value of the subclass' id is used now. (multi tag scene) +- fix: do not generate asg debug when the log level is not debug, now. -## Dependencies upgrade +## Develop behavior change -- [ ] nebula-java: 3.6.0 -> 3.6.1 +- No longer verifying the number of `@Id` in the entity, please keep the number to 1 on your own. + > 不再对实体中的`@Id`个数进行校验,请注意保持个数为1 (含父类) # 1.2.1 diff --git a/README-CN.md b/README-CN.md index 44a042b4..ca727952 100644 --- a/README-CN.md +++ b/README-CN.md @@ -5,21 +5,21 @@ Copyright (c) 2022 All project authors and nebula-contrib. All rights reserved. This source code is licensed under Apache 2.0 License. --> -# NGBATIS +# NgBatis


English | 中文

-- [Ngbatis Docs](https://nebula-contrib.github.io/ngbatis/) -- [Ngbatis 文档](https://graph-cn.github.io/ngbatis-docs/) +- [NgBatis Docs](https://nebula-contrib.github.io/ngbatis/) +- [NgBatis 文档](https://graph-cn.github.io/ngbatis-docs/) ## NGBATIS是什么? -**NGBATIS** 是一款针对 [Nebula Graph](https://github.com/vesoft-inc/nebula) + Springboot 的数据库 ORM 框架。借鉴于 [MyBatis](https://github.com/mybatis/mybatis-3) 的使用习惯进行开发。包含了一些类似于[mybatis-plus](https://github.com/baomidou/mybatis-plus)的单表操作,另外还有一些图特有的实体-关系基本操作。 +**NgBatis** 是一款针对 [Nebula Graph](https://github.com/vesoft-inc/nebula) + Springboot 的数据库 ORM 框架。借鉴于 [MyBatis](https://github.com/mybatis/mybatis-3) 的使用习惯进行开发。包含了一些类似于[mybatis-plus](https://github.com/baomidou/mybatis-plus)的单表操作,另外还有一些图特有的实体-关系基本操作。 如果使用上更习惯于JPA的方式,[graph-ocean](https://github.com/nebula-contrib/graph-ocean) 是个不错的选择。 -## NGBATIS 是怎么运行的? +## NgBatis 是怎么运行的? 请看设计文档 [EXECUTION-PROCESS.md](./EXECUTION-PROCESS.md) @@ -33,6 +33,7 @@ This source code is licensed under Apache 2.0 License. NgBatis | nebula-java | JDK | Springboot | Beetl ---|-------------|---|------------|--- + 1.2.2 | 3.6.0 | 8 | 2.7.0 | 3.15.10.RELEASE 1.2.1-jdk17 | 3.6.0 | 17 | 3.0.7 | 3.15.10.RELEASE 1.2.1 | 3.6.0 | 8 | 2.7.0 | 3.15.10.RELEASE 1.2.0-jdk17 | 3.6.0 | 17 | 3.0.7 | 3.15.10.RELEASE diff --git a/README.md b/README.md index c983dab7..0f436db9 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,18 @@ Copyright (c) 2022 All project authors and nebula-contrib. All rights reserved. This source code is licensed under Apache 2.0 License. --> -# NGBATIS +# NgBatis


English | 中文

-- [Ngbatis Docs](https://nebula-contrib.github.io/ngbatis/) -- [Ngbatis 文档](https://graph-cn.github.io/ngbatis-docs/) +- [NgBatis Docs](https://nebula-contrib.github.io/ngbatis/) +- [NgBatis 文档](https://graph-cn.github.io/ngbatis-docs/) -## What is NGBATIS +## What is NgBatis -**NGBATIS** is a database ORM framework base [NebulaGraph](https://github.com/vesoft-inc/nebula) + spring-boot, which takes advantage of the [mybatis’](https://github.com/mybatis/mybatis-3) fashion development, including some de-factor operations in single table and vertex-edge, like [mybatis-plus](https://github.com/baomidou/mybatis-plus). +**NgBatis** is a database ORM framework base [NebulaGraph](https://github.com/vesoft-inc/nebula) + spring-boot, which takes advantage of the [mybatis’](https://github.com/mybatis/mybatis-3) fashion development, including some de-factor operations in single table and vertex-edge, like [mybatis-plus](https://github.com/baomidou/mybatis-plus). If you prefer JPA, [graph-ocean](https://github.com/nebula-contrib/graph-ocean) is a good choice. @@ -34,6 +34,7 @@ See [EXECUTION-PROCESS.md](./EXECUTION-PROCESS.md) NgBatis | nebula-java | JDK | Springboot | Beetl ---|-------------|---|------------|--- + 1.2.2 | 3.6.0 | 8 | 2.7.0 | 3.15.10.RELEASE 1.2.1-jdk17 | 3.6.0 | 17 | 3.0.7 | 3.15.10.RELEASE 1.2.1 | 3.6.0 | 8 | 2.7.0 | 3.15.10.RELEASE 1.2.0-jdk17 | 3.6.0 | 17 | 3.0.7 | 3.15.10.RELEASE diff --git a/ngbatis-demo/pom.xml b/ngbatis-demo/pom.xml index b22bef5c..2e1cdd7a 100644 --- a/ngbatis-demo/pom.xml +++ b/ngbatis-demo/pom.xml @@ -50,7 +50,7 @@ org.nebula-contrib ngbatis - 1.2.2-SNAPSHOT + 1.2.2 diff --git a/ngbatis-demo/src/main/resources/testgraph.ngql b/ngbatis-demo/src/main/resources/testgraph.ngql index a0d6c7ed..4e15a8a9 100644 --- a/ngbatis-demo/src/main/resources/testgraph.ngql +++ b/ngbatis-demo/src/main/resources/testgraph.ngql @@ -1,7 +1,10 @@ create space if not exists test (vid_type = fixed_string(32)) :sleep 20 -use test -create tag if not exists person(name string,gender string,height double,age int32 ,birthday datetime) -create tag if not exists employee(name string,gender string,height double,age int32 ,birthday datetime,position string) -create edge if not exists like(likeness double) -create tag index person_index_1 on person(age,birthday) \ No newline at end of file +use test; +create tag if not exists person(name string,gender string,height double,age int32 ,birthday datetime); +create tag if not exists employee(name string,gender string,height double,age int32 ,birthday datetime,position string); +create edge if not exists like(likeness double); +create tag index person_index_1 on person(age,birthday); + +CREATE tag `column_alias` (`first_name` string NULL , `last_name` string NULL ); +CREATE TAG INDEX `i_column_alias_first_name` ON `column_alias`(`first_name`(50)); \ No newline at end of file diff --git a/ngbatis-demo/src/test/java/ye/weicheng/ngbatis/demo/repository/ColumnAliasDaoTest.java b/ngbatis-demo/src/test/java/ye/weicheng/ngbatis/demo/repository/ColumnAliasDaoTest.java index 05082d41..cda71147 100644 --- a/ngbatis-demo/src/test/java/ye/weicheng/ngbatis/demo/repository/ColumnAliasDaoTest.java +++ b/ngbatis-demo/src/test/java/ye/weicheng/ngbatis/demo/repository/ColumnAliasDaoTest.java @@ -4,7 +4,11 @@ // // This source code is licensed under Apache 2.0 License. +import java.util.List; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.locationtech.jts.util.Assert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -16,26 +20,97 @@ *
Now is history! */ @SpringBootTest +@TestMethodOrder(OrderAnnotation.class) public class ColumnAliasDaoTest { @Autowired private ColumnAliasDao dao; + static final String idNo = "ID" + System.currentTimeMillis(); + static final String firstName = Math.random() + ""; @Test - public void updateById() { - long now = System.currentTimeMillis(); - String idNo = "UBI" + now; + @Order(1) + public void insert() { ColumnAlias colAliasPojo = new ColumnAlias(); colAliasPojo.setIdNo(idNo); dao.insert(colAliasPojo); + } - String firstName = Math.random() + ""; + @Test + @Order(2) + public void updateById() { + ColumnAlias colAliasPojo = new ColumnAlias(); + colAliasPojo.setIdNo(idNo); colAliasPojo.setFirstName(firstName); dao.updateById(colAliasPojo); - + } + + @Test + @Order(3) + public void selectById() { ColumnAlias colAliasPojoDb = dao.selectById(idNo); - Assert.isTrue(firstName.equals(colAliasPojoDb.getFirstName())); + Assert.isTrue(idNo.equals(colAliasPojoDb.getIdNo())); + } + + @Test + @Order(4) + public void selectBySelective() { + ColumnAlias colAliasPojo = new ColumnAlias(); + colAliasPojo.setIdNo(idNo); + colAliasPojo.setFirstName(firstName); + List nodesInDb = dao.selectBySelective(colAliasPojo); + Assert.isTrue(nodesInDb.size() > 0); + nodesInDb.forEach(node -> { + Assert.isTrue(node.getFirstName().equals(firstName)); + Assert.isTrue(node.getIdNo().equals(idNo)); + }); + } + + @Test + @Order(5) + public void selectIdBySelective() { + ColumnAlias colAliasPojo = new ColumnAlias(); + colAliasPojo.setIdNo(idNo); + colAliasPojo.setFirstName(firstName); + List idsInDb = dao.selectIdBySelective(colAliasPojo); + Assert.isTrue(idsInDb.contains(idNo)); + } + + @Test + @Order(6) + public void selectBySelectiveStringLike() { + ColumnAlias colAliasPojo = new ColumnAlias(); + String query = firstName.substring(0, 5); + colAliasPojo.setFirstName(query); + List nodesInDb = dao.selectBySelectiveStringLike(colAliasPojo); + Assert.isTrue(nodesInDb.size() > 0); + nodesInDb.forEach(node -> Assert.isTrue(node.getFirstName().contains(query))); + } + + // selectIdBySelectiveStringLike + @Test + @Order(7) + public void selectIdBySelectiveStringLike() { + ColumnAlias colAliasPojo = new ColumnAlias(); + colAliasPojo.setIdNo(idNo); + String query = firstName.substring(0, 5); + colAliasPojo.setFirstName(query); + List idsInDb = dao.selectIdBySelectiveStringLike(colAliasPojo); + Assert.isTrue(idsInDb.contains(idNo)); + } + + @Test + @Order(99) + public void deleteById() { + dao.deleteById(idNo); + } + + @Test + @Order(100) + public void selectByIdAfterDelete() { + ColumnAlias colAliasPojoDb = dao.selectById(idNo); + Assert.isTrue(colAliasPojoDb == null); } } diff --git a/pom.xml b/pom.xml index 036fb5fb..b5c2df31 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ ngbatis org.nebula-contrib ngbatis - 1.2.2-SNAPSHOT + 1.2.2 NgBatis is a database ORM framework base NebulaGraph + spring-boot, diff --git a/src/main/java/org/nebula/contrib/ngbatis/binding/DefaultArgsResolver.java b/src/main/java/org/nebula/contrib/ngbatis/binding/DefaultArgsResolver.java index e8433e4f..11bd1d86 100644 --- a/src/main/java/org/nebula/contrib/ngbatis/binding/DefaultArgsResolver.java +++ b/src/main/java/org/nebula/contrib/ngbatis/binding/DefaultArgsResolver.java @@ -4,6 +4,7 @@ // // This source code is licensed under Apache 2.0 License. +import static org.nebula.contrib.ngbatis.utils.ReflectUtil.getAllColumnFields; import static org.nebula.contrib.ngbatis.utils.ReflectUtil.isCurrentTypeOrParentType; import static org.nebula.contrib.ngbatis.utils.ReflectUtil.typeArg; @@ -132,8 +133,8 @@ public void init() { put(Object.class, (Setter) (obj) -> { Map pojoFields = new HashMap<>(); Class paramType = obj.getClass(); - Field[] declaredFields = paramType.getDeclaredFields(); - for (Field declaredField : declaredFields) { + Field[] allFields = getAllColumnFields(paramType); + for (Field declaredField : allFields) { Object nebulaValue = toNebulaValueType( ReflectUtil.getValue(obj, declaredField), declaredField diff --git a/src/main/java/org/nebula/contrib/ngbatis/binding/beetl/functions/KvFn.java b/src/main/java/org/nebula/contrib/ngbatis/binding/beetl/functions/KvFn.java index d6389489..84a27191 100644 --- a/src/main/java/org/nebula/contrib/ngbatis/binding/beetl/functions/KvFn.java +++ b/src/main/java/org/nebula/contrib/ngbatis/binding/beetl/functions/KvFn.java @@ -104,6 +104,10 @@ public KV recordToKV(Object record, Iterable fields, boolean selective, S List currentTagColumns = kv.multiTagColumns .computeIfAbsent(tagName, (k) -> new ArrayList<>()); currentTagColumns.add(name); + + List currentTagFields = kv.multiTagFields + .computeIfAbsent(tagName, (k) -> new ArrayList<>()); + currentTagFields.add(field.getName()); kv.columns.add(name); Object[] paras = {value}; @@ -139,7 +143,11 @@ private void initFieldGroups(Object record, KV kv) { } public static class KV { + // 以 tagName 为 key,列名列表为 value + // 使用 LinkedHashMap 保证顺序,使得推入字段的顺序与 columns、values、types 一致 public final Map> multiTagColumns = new LinkedHashMap<>(); + // 以 tagName 为 key,属性名列表为 value + public final Map> multiTagFields = new LinkedHashMap<>(); public final List columns = new ArrayList<>(); public final List valueNames = new ArrayList<>(); public final List values = new ArrayList<>(); diff --git a/src/main/java/org/nebula/contrib/ngbatis/proxy/MapperProxyClassGenerator.java b/src/main/java/org/nebula/contrib/ngbatis/proxy/MapperProxyClassGenerator.java index 5ea1c1f0..71ad730f 100644 --- a/src/main/java/org/nebula/contrib/ngbatis/proxy/MapperProxyClassGenerator.java +++ b/src/main/java/org/nebula/contrib/ngbatis/proxy/MapperProxyClassGenerator.java @@ -17,6 +17,8 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * 基于 ASM 对接口进行动态代理,并生成 Bean 代理的实现类 @@ -25,6 +27,8 @@ *
Now is history! */ public class MapperProxyClassGenerator implements Opcodes { + + private final Logger log = LoggerFactory.getLogger(MapperProxyClassGenerator.class); /** * 获取DAO接口对应的动态代理类名称 @@ -74,7 +78,9 @@ public byte[] setClassCode(ClassModel cm) { byte[] code = cw.toByteArray(); cm.setClassByte(code); - writeFile(cm); + if (log.isDebugEnabled()) { + writeFile(cm); + } return code; } diff --git a/src/main/java/org/nebula/contrib/ngbatis/utils/ReflectUtil.java b/src/main/java/org/nebula/contrib/ngbatis/utils/ReflectUtil.java index 47c0db78..0af4cd66 100644 --- a/src/main/java/org/nebula/contrib/ngbatis/utils/ReflectUtil.java +++ b/src/main/java/org/nebula/contrib/ngbatis/utils/ReflectUtil.java @@ -323,7 +323,7 @@ public static Set> getParentTypes(Class paramType) { * 实体类获取全部属性,对父类获取 带 @Column 的属性 * * @param clazz 实体类 - * @return 当前类的属性及其父类中,带@Column注解的属性 + * @return 当前类及其父类的属性,排除 {@link Transient} 注解的属性 */ public static Field[] getAllColumnFields(Class clazz) { return getAllColumnFields(clazz, false); @@ -334,7 +334,8 @@ public static Field[] getAllColumnFields(Class clazz) { * * @param clazz 实体类 * @param forValueSetting 用于设值时为 true,读取全属性 - * @return 当前类的属性及其父类中,带@Column注解的属性 + * 否则只过滤{@link Transient}注解的属性 + * @return 当前类及其父类的属性 */ public static Field[] getAllColumnFields(Class clazz, boolean forValueSetting) { Set fields = new LinkedHashSet<>(); @@ -413,11 +414,19 @@ public static Field getPkField(Field[] fields, Class type) { */ public static Field getPkField(Field[] fields, Class type, boolean canNotNull) { Field pkField = null; + Field typePkField = null; for (Field field : fields) { if (field.isAnnotationPresent(Id.class)) { pkField = field; + if (field.getDeclaringClass().equals(type)) { + typePkField = field; + } } } + // 多标签时,以运行时类中的 @Id 注解为准 + if (typePkField != null) { + pkField = typePkField; + } if (canNotNull && pkField == null) { throw new ParseException( String.format("%s 必须有一个属性用 @Id 注解。(javax.persistence.Id)", type)); diff --git a/src/main/java/org/nebula/contrib/ngbatis/utils/ResultSetUtil.java b/src/main/java/org/nebula/contrib/ngbatis/utils/ResultSetUtil.java index aaef80bc..df54a162 100644 --- a/src/main/java/org/nebula/contrib/ngbatis/utils/ResultSetUtil.java +++ b/src/main/java/org/nebula/contrib/ngbatis/utils/ResultSetUtil.java @@ -344,10 +344,15 @@ public static void relationshipToResultType(Object o, String fieldName, */ public static void setId(Object obj, Class resultType, Node v) throws IllegalAccessException { - Field pkField = getPkField(resultType); - ValueWrapper idWrapper = v.getId(); - Object id = ResultSetUtil.getValue(idWrapper); - ReflectUtil.setValue(obj, pkField, id); + Field pkField = getPkField(resultType, false); + if (pkField != null) { + ValueWrapper idWrapper = v.getId(); + Object id = ResultSetUtil.getValue(idWrapper); + ReflectUtil.setValue(obj, pkField, id); + } + if (resultType.getSuperclass() != null) { + setId(obj, resultType.getSuperclass(), v); + } } /** @@ -362,11 +367,13 @@ public static void setId(Object obj, Class resultType, Node v) public static void setRanking(Object obj, Class resultType, Relationship e) throws IllegalAccessException { Field pkField = getPkField(resultType, false); - if (pkField == null) { - return; + if (pkField != null) { + long ranking = e.ranking(); + ReflectUtil.setValue(obj, pkField, ranking); + } + if (resultType.getSuperclass() != null) { + setRanking(obj, resultType.getSuperclass(), e); } - long ranking = e.ranking(); - ReflectUtil.setValue(obj, pkField, ranking); } /** diff --git a/src/main/resources/NebulaDaoBasic.xml b/src/main/resources/NebulaDaoBasic.xml index 3bda8fa9..6b964812 100644 --- a/src/main/resources/NebulaDaoBasic.xml +++ b/src/main/resources/NebulaDaoBasic.xml @@ -20,13 +20,16 @@ @var kv = ng.kv( ng_args[0], '', true, true, false ); @var id = ng.id( ng_args[0], true, false ); - @var pkName = ng.pkName( ng_args[0] ); + @var pkName = ng.pkName( ng_args[0], false ); @var tagName = ng.tagName( ng_args[0] ); + @var columns = @kv.multiTagColumns.get( tagName ); MATCH (n:`${ tagName }`) - WHERE 1 == 1 ${ isNotEmpty( id ) ? ('\n and id(n) == $' + pkName) : '' } - @if ( isNotEmpty( @kv.columns ) ) { - @for ( col in @kv.columns ) { - and n.`${ tagName }`.${ col } ${ - ng.ifStringLike( - @kv.values.get( colLP.dataIndex ), - @kv.types.get( colLP.dataIndex ), - @kv.valueNames.get( colLP.dataIndex ) - ) - } + WHERE 1 == 1 ${ isNotEmpty( id ) ? ('\n and id(n)' + ng.ifStringLike( id )) : '' } + @if ( isNotEmpty( @kv.multiTagColumns ) ) { + @for ( tagEntry in @kv.multiTagColumns ) { + @for ( col in tagEntry.value ) { + @var vIdx = @kv.columns.indexOf( col ); + and n.`${ tagEntry.key }`.`${ col }` ${ + ng.ifStringLike( + @kv.values.get( vIdx ), + @kv.types.get( vIdx ), + @kv.multiTagFields.get( tagEntry.key ).get(colLP.index - 1) + ) + } + @} @} @} RETURN n @@ -58,13 +65,16 @@ @var kv = ng.kv( ng_args[0], '', true, true, false ); @var id = ng.id( ng_args[0], true, false ); - @var pkName = ng.pkName( ng_args[0] ); + @var pkName = ng.pkName( ng_args[0], false ); @var tagName = ng.tagName( ng_args[0] ); + @var columns = @kv.multiTagColumns.get( tagName ); MATCH (n:`${ tagName }`) WHERE 1 == 1 ${ isNotEmpty( id ) ? ('\n and id(n)' + ng.ifStringLike( id )) : '' } - @if ( isNotEmpty( @kv.columns ) ) { - @for ( col in @kv.columns ) { - and n.`${ tagName }`.${ col } ${ - ng.ifStringLike( - @kv.values.get( colLP.dataIndex ), - @kv.types.get( colLP.dataIndex ), - @kv.valueNames.get( colLP.dataIndex ) - ) - } + @if ( isNotEmpty( @kv.multiTagColumns ) ) { + @for ( tagEntry in @kv.multiTagColumns ) { + @for ( col in tagEntry.value ) { + @var vIdx = @kv.columns.indexOf( col ); + and n.`${ tagEntry.key }`.`${ col }` ${ + ng.ifStringLike( + @kv.values.get( vIdx ), + @kv.types.get( vIdx ), + @kv.multiTagFields.get( tagEntry.key ).get(colLP.index - 1) + ) + } + @} @} @} RETURN id(n)