Prechádzať zdrojové kódy

支持SqlRunner单参数属性提取.

nieqiurong 1 mesiac pred
rodič
commit
f4ed853dbd

+ 102 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/assist/ISqlRunner.java

@@ -29,35 +29,137 @@ import java.util.Map;
  */
 public interface ISqlRunner {
 
+    /**
+     * INSERT 语句
+     */
     String INSERT = "com.baomidou.mybatisplus.core.mapper.SqlRunner.Insert";
+
+    /**
+     * DELETE 语句
+     */
     String DELETE = "com.baomidou.mybatisplus.core.mapper.SqlRunner.Delete";
+
+    /**
+     * UPDATE 语句
+     */
     String UPDATE = "com.baomidou.mybatisplus.core.mapper.SqlRunner.Update";
+
+    /**
+     * SELECT_LIST 语句
+     */
     String SELECT_LIST = "com.baomidou.mybatisplus.core.mapper.SqlRunner.SelectList";
+
+    /**
+     * SELECT_OBJS 语句
+     */
     String SELECT_OBJS = "com.baomidou.mybatisplus.core.mapper.SqlRunner.SelectObjs";
+
+    /**
+     * COUNT 语句
+     */
     String COUNT = "com.baomidou.mybatisplus.core.mapper.SqlRunner.Count";
 
     /**
+     * 注入SQL脚本
+     *
      * @deprecated 3.5.12 {@link SqlRunnerInjector#SQL_SCRIPT}
      */
+    @Deprecated
     String SQL_SCRIPT = "${sql}";
+
+    /**
+     * sql访问参数
+     */
     String SQL = "sql";
+
+    /**
+     * page访问参数
+     */
     String PAGE = "page";
 
+    /**
+     * 执行插入语句
+     *
+     * @param sql  指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 插入结果
+     */
     boolean insert(String sql, Object... args);
 
+    /**
+     * 执行删除语句
+     *
+     * @param sql  指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 删除结果
+     */
     boolean delete(String sql, Object... args);
 
+    /**
+     * 执行更新语句
+     *
+     * @param sql  指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 更新结果
+     */
     boolean update(String sql, Object... args);
 
+    /**
+     * 根据sql查询Map结果集
+     * <p>SqlRunner.db().selectList("select * from tbl_user where name={0}", "Caratacus")</p>
+     *
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数列表
+     * @return 结果集
+     */
     List<Map<String, Object>> selectList(String sql, Object... args);
 
+    /**
+     * 根据sql查询一个字段值的结果集
+     * <p>注意:该方法只会返回一个字段的值, 如果需要多字段,请参考{@link #selectList(String, Object...)}</p>
+     *
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 结果集
+     */
     List<Object> selectObjs(String sql, Object... args);
 
+    /**
+     * 根据sql查询一个字段值的一条结果
+     * <p>注意:该方法只会返回一个字段的值, 如果需要多字段,请参考{@link #selectOne(String, Object...)}</p>
+     *
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 结果
+     */
     Object selectObj(String sql, Object... args);
 
+    /**
+     * 查询总数
+     *
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 总记录数
+     */
     long selectCount(String sql, Object... args);
 
+    /**
+     * 获取单条记录
+     *
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 单行结果集 (当执行语句返回多条记录时,只会选取第一条记录)
+     */
     Map<String, Object> selectOne(String sql, Object... args);
 
+    /**
+     * 分页查询
+     *
+     * @param page 分页对象
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @param <E>  E
+     * @return 分页数据
+     */
     <E extends IPage<Map<String, Object>>> E selectPage(E page, String sql, Object... args);
 }

+ 2 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/injector/SqlRunnerInjector.java

@@ -42,6 +42,8 @@ public class SqlRunnerInjector {
     protected LanguageDriver languageDriver;
 
     /**
+     * 注入动态执行脚本
+     *
      * @since 3.5.12
      */
     public static final String SQL_SCRIPT = "<script>" +

+ 124 - 27
mybatis-plus-spring/src/main/java/com/baomidou/mybatisplus/extension/toolkit/SqlRunner.java

@@ -22,20 +22,31 @@ import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
 import org.apache.ibatis.logging.Log;
 import org.apache.ibatis.logging.LogFactory;
 import org.apache.ibatis.parsing.GenericTokenParser;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.reflection.SystemMetaObject;
 import org.apache.ibatis.session.SqlSession;
 import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.type.SimpleTypeRegistry;
 import org.mybatis.spring.SqlSessionUtils;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
 /**
  * SqlRunner 执行 SQL
+ * <p>
+ * 自3.5.12开始,(当传入的参数是单参数时,支持使用Map,Array,List,JavaBean)
+ * <li>当参数为 Map 时可通过{key}进行属性访问
+ * <li>当参数为 JavaBean 时可通过{property}进行属性访问
+ * <li>当参数为 List 时直接访问索引 {0} </li>
+ * </p>
  *
- * @author Caratacus
+ * @author Caratacus, nieqiurong
  * @since 2016-12-11
  */
 public class SqlRunner implements ISqlRunner {
@@ -45,6 +56,9 @@ public class SqlRunner implements ISqlRunner {
     // 单例Query
     public static final SqlRunner DEFAULT = new SqlRunner();
 
+    /**
+     * 实体类 (当未指定时,将使用{@link SqlHelper#FACTORY}进行会话操作)
+     */
     private Class<?> clazz;
 
     public SqlRunner() {
@@ -57,7 +71,7 @@ public class SqlRunner implements ISqlRunner {
     /**
      * 获取默认的SqlQuery(适用于单库)
      *
-     * @return ignore
+     * @return this
      */
     public static SqlRunner db() {
         return DEFAULT;
@@ -66,15 +80,22 @@ public class SqlRunner implements ISqlRunner {
     /**
      * 根据当前class对象获取SqlQuery(适用于多库)
      *
-     * @param clazz ignore
-     * @return ignore
+     * @param clazz 实体类
+     * @return this
      */
     public static SqlRunner db(Class<?> clazz) {
         return new SqlRunner(clazz);
     }
 
-    @Transactional
+    /**
+     * 执行插入语句
+     *
+     * @param sql  指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 插入结果
+     */
     @Override
+    @Transactional
     public boolean insert(String sql, Object... args) {
         SqlSession sqlSession = sqlSession();
         try {
@@ -84,8 +105,15 @@ public class SqlRunner implements ISqlRunner {
         }
     }
 
-    @Transactional
+    /**
+     * 执行删除语句
+     *
+     * @param sql  指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 删除结果
+     */
     @Override
+    @Transactional
     public boolean delete(String sql, Object... args) {
         SqlSession sqlSession = sqlSession();
         try {
@@ -97,10 +125,16 @@ public class SqlRunner implements ISqlRunner {
 
     /**
      * 获取sqlMap参数
+     * <p>
+     * 自3.5.12开始,(当传入的参数是单参数时,支持使用Map,Array,List,JavaBean)
+     * <li>当参数为 Map 时可通过{key}进行属性访问
+     * <li>当参数为 JavaBean 时可通过{property}进行属性访问
+     * <li>当参数为 List 时直接访问索引 {0} </li>
+     * </p>
      *
-     * @param sql  指定参数的格式: {0}, {1}
-     * @param args 仅支持String
-     * @return ignore
+     * @param sql  指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 参数集合
      */
     private Map<String, Object> sqlMap(String sql, Object... args) {
         Map<String, Object> sqlMap = getParams(args);
@@ -111,8 +145,9 @@ public class SqlRunner implements ISqlRunner {
     /**
      * 获取执行语句
      *
-     * @param sql    原始sql
+     * @param sql 原始sql
      * @return 执行语句
+     * @since 3.5.12
      */
     private String parse(String sql) {
         return new GenericTokenParser("{", "}", content -> "#{" + content + "}").parse(sql);
@@ -121,12 +156,44 @@ public class SqlRunner implements ISqlRunner {
     /**
      * 获取参数列表
      *
-     * @param args 参数
+     * @param args 参数(单参数时,支持使用Map,List,JavaBean访问)
      * @return 参数map
      * @since 3.5.12
      */
     private Map<String, Object> getParams(Object... args) {
-        if (args != null) {
+        if (args != null && args.length > 0) {
+            if (args.length == 1) {
+                // 暂定支持 Map,Collection,JavaBean
+                Object arg = args[0];
+                if (arg instanceof Map) {
+                    //noinspection unchecked
+                    return new HashMap<String, Object>((Map) arg);
+                }
+                if (arg instanceof Collection) {
+                    Collection<?> collection = (Collection<?>) arg;
+                    Map<String, Object> params = new HashMap<>(CollectionUtils.newHashMapWithExpectedSize(collection.size()));
+                    Iterator<?> iterator = collection.iterator();
+                    int index = 0;
+                    while (iterator.hasNext()) {
+                        params.put(String.valueOf(index), iterator.next());
+                        index++;
+                    }
+                    return params;
+                }
+                Class<?> cls = arg.getClass();
+                if (!(cls.isPrimitive()
+                    || SimpleTypeRegistry.isSimpleType(cls)
+                    || cls.isArray() || cls.isEnum())
+                ) {
+                    MetaObject metaObject = SystemMetaObject.forObject(arg);
+                    String[] getterNames = metaObject.getGetterNames();
+                    Map<String, Object> params = new HashMap<>(CollectionUtils.newHashMapWithExpectedSize(getterNames.length));
+                    for (String getterName : getterNames) {
+                        params.put(getterName, metaObject.getValue(getterName));
+                    }
+                    return params;
+                }
+            }
             Map<String, Object> params = CollectionUtils.newHashMapWithExpectedSize(args.length);
             for (int i = 0; i < args.length; i++) {
                 params.put(String.valueOf(i), args[i]);
@@ -139,10 +206,10 @@ public class SqlRunner implements ISqlRunner {
     /**
      * 获取sqlMap参数
      *
-     * @param sql  指定参数的格式: {0}, {1}
+     * @param sql  指定参数的格式: {0}, {1} 或者 {property1}, {property2}
      * @param page 分页模型
-     * @param args 仅支持String
-     * @return ignore
+     * @param args 参数
+     * @return 参数集合
      */
     private Map<String, Object> sqlMap(String sql, IPage<?> page, Object... args) {
         Map<String, Object> sqlMap = getParams(args);
@@ -151,8 +218,15 @@ public class SqlRunner implements ISqlRunner {
         return sqlMap;
     }
 
-    @Transactional
+    /**
+     * 执行更新语句
+     *
+     * @param sql  指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 更新结果
+     */
     @Override
+    @Transactional
     public boolean update(String sql, Object... args) {
         SqlSession sqlSession = sqlSession();
         try {
@@ -166,9 +240,9 @@ public class SqlRunner implements ISqlRunner {
      * 根据sql查询Map结果集
      * <p>SqlRunner.db().selectList("select * from tbl_user where name={0}", "Caratacus")</p>
      *
-     * @param sql  sql语句,可添加参数,格式:{0},{1}
-     * @param args 只接受String格式
-     * @return ignore
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数列表
+     * @return 结果集
      */
     @Override
     public List<Map<String, Object>> selectList(String sql, Object... args) {
@@ -182,11 +256,11 @@ public class SqlRunner implements ISqlRunner {
 
     /**
      * 根据sql查询一个字段值的结果集
-     * <p>注意:该方法只会返回一个字段的值, 如果需要多字段,请参考{@code selectList()}</p>
+     * <p>注意:该方法只会返回一个字段的值, 如果需要多字段,请参考{@link #selectList(String, Object...)}</p>
      *
-     * @param sql  sql语句,可添加参数,格式:{0},{1}
-     * @param args 只接受String格式
-     * @return ignore
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 结果集
      */
     @Override
     public List<Object> selectObjs(String sql, Object... args) {
@@ -200,17 +274,24 @@ public class SqlRunner implements ISqlRunner {
 
     /**
      * 根据sql查询一个字段值的一条结果
-     * <p>注意:该方法只会返回一个字段的值, 如果需要多字段,请参考{@code selectOne()}</p>
+     * <p>注意:该方法只会返回一个字段的值, 如果需要多字段,请参考{@link #selectOne(String, Object...)}</p>
      *
-     * @param sql  sql语句,可添加参数,格式:{0},{1}
-     * @param args 只接受String格式
-     * @return ignore
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 结果
      */
     @Override
     public Object selectObj(String sql, Object... args) {
         return SqlHelper.getObject(LOG, selectObjs(sql, args));
     }
 
+    /**
+     * 查询总数
+     *
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 总记录数
+     */
     @Override
     public long selectCount(String sql, Object... args) {
         SqlSession sqlSession = sqlSession();
@@ -221,11 +302,27 @@ public class SqlRunner implements ISqlRunner {
         }
     }
 
+    /**
+     * 获取单条记录
+     *
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @return 单行结果集 (当执行语句返回多条记录时,只会选取第一条记录)
+     */
     @Override
     public Map<String, Object> selectOne(String sql, Object... args) {
         return SqlHelper.getObject(LOG, selectList(sql, args));
     }
 
+    /**
+     * 分页查询
+     *
+     * @param page 分页对象
+     * @param sql  sql语句,可添加参数,指定参数的格式: {0}, {1} 或者 {property1}, {property2}
+     * @param args 参数
+     * @param <E>  E
+     * @return 分页数据
+     */
     @Override
     public <E extends IPage<Map<String, Object>>> E selectPage(E page, String sql, Object... args) {
         if (null == page) {

+ 84 - 0
mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/h2/SqlRunnerTest.java

@@ -6,7 +6,10 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.toolkit.SqlRunner;
 import com.baomidou.mybatisplus.test.h2.entity.H2Student;
+import com.baomidou.mybatisplus.test.h2.enums.AgeEnum;
 import com.baomidou.mybatisplus.test.h2.service.IH2StudentService;
+import lombok.AllArgsConstructor;
+import lombok.Data;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -16,6 +19,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * SqlRunner测试
@@ -93,4 +97,84 @@ class SqlRunnerTest {
         Assertions.assertEquals(10004L, SqlRunner.db().selectObj("select id from h2student where name = {0}", name));
     }
 
+    @Test
+    @Order(7)
+    void testByMap() {
+        var map = Map.of("name", "test", "age", AgeEnum.TWO, "id", 11000L);
+        Assertions.assertTrue(SqlRunner.db().insert("INSERT INTO h2student (id, name, age ) VALUES ( {id}, {name}, {age} )", map));
+    }
+
+    @Test
+    @Order(8)
+    void testByEntity() {
+        var entity = new H2Student();
+        entity.setId(11001L);
+        entity.setName("test");
+        entity.setAge(12);
+        Assertions.assertTrue(SqlRunner.db().insert("INSERT INTO h2student (id, name, age ) VALUES ( {id}, {name}, {age} )", entity));
+    }
+
+    @Data
+    @AllArgsConstructor
+    static class StudentDto {
+
+        private Long id;
+
+        private String name;
+
+        private AgeEnum age;
+    }
+
+    @Test
+    @Order(9)
+    void testByDto() {
+        var studentDto = new StudentDto(11002L, "测试学生", AgeEnum.THREE);
+        Assertions.assertTrue(SqlRunner.db().insert("INSERT INTO h2student (id, name, age ) VALUES ( {id}, {name}, {age} )", studentDto));
+        Map<String, Object> resultMap = SqlRunner.db().selectOne("select * from h2student where id = {id}", studentDto);
+        Assertions.assertNotNull(resultMap);
+        Assertions.assertEquals(studentDto.getName(), resultMap.get("NAME"));
+        Assertions.assertEquals(studentDto.getAge().getValue(), resultMap.get("AGE"));
+        Assertions.assertEquals(studentDto.getId(), resultMap.get("ID"));
+    }
+
+    @Test
+    @Order(10)
+    void testByArray() {
+        var array = new Object[]{11003L, "测试学生", AgeEnum.THREE};
+        Assertions.assertTrue(SqlRunner.db().insert("INSERT INTO h2student (id, name, age ) VALUES ( {0}, {1}, {2} )", array));
+        Map<String, Object> resultMap = SqlRunner.db().selectOne("select * from h2student where id = {0}", array);
+        Assertions.assertNotNull(resultMap);
+        Assertions.assertEquals("测试学生", resultMap.get("NAME"));
+        Assertions.assertEquals(AgeEnum.THREE.getValue(), resultMap.get("AGE"));
+        Assertions.assertEquals(11003L, resultMap.get("ID"));
+    }
+
+    @Test
+    @Order(11)
+    void testByList() {
+        var list = List.of(11004L, "测试学生", AgeEnum.THREE);
+        Assertions.assertTrue(SqlRunner.db().insert("INSERT INTO h2student (id, name, age ) VALUES ( {0}, {1}, {2} )", list));
+        Map<String, Object> resultMap = SqlRunner.db().selectOne("select * from h2student where id = {0}", list);
+        Assertions.assertNotNull(resultMap);
+        Assertions.assertEquals("测试学生", resultMap.get("NAME"));
+        Assertions.assertEquals(AgeEnum.THREE.getValue(), resultMap.get("AGE"));
+        Assertions.assertEquals(11004L, resultMap.get("ID"));
+    }
+
+    record StudentDtoRecord(Long id, String name, AgeEnum age) {
+
+    }
+
+    @Test
+    @Order(12)
+    void testByRecord() {
+        var studentDto = new StudentDtoRecord(11005L, "测试学生", AgeEnum.THREE);
+        Assertions.assertTrue(SqlRunner.db().insert("INSERT INTO h2student (id, name, age ) VALUES ( {id}, {name}, {age} )", studentDto));
+        Map<String, Object> resultMap = SqlRunner.db().selectOne("select * from h2student where id = {id}", studentDto);
+        Assertions.assertNotNull(resultMap);
+        Assertions.assertEquals(studentDto.name, resultMap.get("NAME"));
+        Assertions.assertEquals(studentDto.age.getValue(), resultMap.get("AGE"));
+        Assertions.assertEquals(studentDto.id, resultMap.get("ID"));
+    }
+
 }