# query-pro **Repository Path**: cloudself/query-pro ## Basic Information - **Project Name**: query-pro - **Description**: 一个简单的数据库操作工具 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-12-20 - **Last Updated**: 2024-10-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # QueryPro [![Maven central](https://maven-badges.herokuapp.com/maven-central/cn.cloudself/query-pro/badge.svg)](https://maven-badges.herokuapp.com/maven-central/cn.cloudself/query-pro) QueryPro 是一个ORM和SQL的工具集,旨在快速开发。 它支持分批插入,逻辑删除,多表查询,多作用域下的配置(包括切换数据源,日志输出等),以及生命周期方法等功能。 在一定程度上,它比`MyBatis(Plus)`更加方便且更容易阅读。 另外,它在语言设计上部分参考了`Spring Data JPA`,但添加了部分`Spring Data JPA`没有的功能。 这是它的简单使用方法: ```java // 等同于 SELECT * FROM `user` WHERE `id` = ? LIMIT 1 User user = UserQueryPro.selectByPrimaryKey(1); // 等同于 SELECT * FROM `user` WHERE `user`.`username` = ? List users = UserQueryPro.selectBy().username().is().equalTo("hello").run(); // 等同于 SELECT * FROM `user` WHERE `user`.`id` = ? OR `user`.`age` = ? List users2 = UserQueryPro .selectBy().id().eq(1) // is 是可选的 .or().age().not().in(10, 11) .run(); ``` ## 快速入门 & 文档 ### 快速生成文件 QueryPro需要生成`entity`文件和`dao`文件,这些文件无需修改且不建议修改,推荐放在单独的模块中。 ```java QueryProFileMaker /* 将文件生成至 /src/main/java/cn/cloudself/demo/的dao包和entity包下 */ .javaEntityAndDaoMode(PathFrom.javaPackage("cn.cloudself.demo")) .db( DbInfoBuilder .mysql("127.0.0.1", "query_pro_test") .driver("com.mysql.cj.jdbc.Driver") .toDbInfo("root", Password.password1) ) /* *代表所有表 */ .tables("*") /* 给Entity添加后缀 */ .dbJavaNameConverter(DbNameToJava.createDefault().addSuffixToEntity("Entity").getConverter()) /* 直接覆盖原有文件 */ .replaceMode() /* 更多的日志输出 */ .debug() .create(); ``` ### 单表查询 #### 使用主键查询 ```java // SELECT * FROM `user` WHERE `id` = ? LIMIT 1 User user0 = UserQueryPro.selectByPrimaryKey(1); ``` #### 使用比较运算符查询 ```java // = // SELECT * FROM `user` WHERE `user`.`id` = ? List users1_1 = UserQueryPro.selectBy().id().is().equalTo(1).run(); // 最完整的写法 List users1_2 = UserQueryPro.selectBy().id().equalTo(1).run(); // 所有的is都是可以省略的 List users1_3 = UserQueryPro.selectBy().id().eq(1).run(); // eq 是equal的缩写 List users1_4 = UserQueryPro.selectBy().id(1).run(); // 也可以省略 eq // in // SELECT * FROM `user` WHERE `user`.`name` IN (?, ?) List users2_1 = UserQueryPro.selectBy().name().in("hb", "herb").run(); // in 支持数组或可变参数数组 List users2_2 = UserQueryPro.selectBy().name().in(userNameList).run(); // in 也支持 Collection List users2_3 = UserQueryPro.selectBy().name("hb", "herb").run(); // 也可以直接省略in // >, <, >=, <=, like, between // SELECT * FROM `user` WHERE `user`.`age` > ? List users3_1 = UserQueryPro.selectBy().age().graterThan(18).run(); // SELECT * FROM `user` WHERE `user`.`age` < ? List users3_2 = UserQueryPro.selectBy().age().lessThan(18).run(); // SELECT * FROM `user` WHERE `user`.`age` >= ? List users3_3 = UserQueryPro.selectBy().age().graterThanOrEqual(18).run(); // SELECT * FROM `user` WHERE `user`.`age` <= ? List users3_4 = UserQueryPro.selectBy().age().lessThanOrEqual(18).run(); // SELECT * FROM `user` WHERE `user`.`name` LIKE ? ORDER BY `user`.`id` DESC List users3_5 = UserQueryPro.selectBy().name().like("%h%").run(); // SELECT * FROM `user` WHERE `user`.`age` BETWEEN (?, ?) List users3_6 = UserQueryPro.selectBy().age().between(18, 20).run(); ``` #### AND和OR运算符 ```java // 使用and查询 // !!! and是可以省略的,但有时候加着更好看 // SELECT * FROM `user` WHERE `user`.`name` = ? AND `user`.`age` = ? List usersRun3 = UserQueryPro .selectBy().name().is().equalTo("hb") .and().age().is().equalTo(18) .run(); // 使用or查询 // SELECT * FROM `user` WHERE `user`.`id` = ? OR `user`.`age` = ? List usersRun5_1 = UserQueryPro.selectBy().id().is().equalTo(1).or().age().equalTo(10).run(); // SELECT * FROM `user` WHERE `user`.`id` = ? OR (`user`.`age` = ? AND `user`.`name` LIKE ?) List usersRun5_2 = UserQueryPro .selectBy().id().is().equalTo(1) .or((it) -> it.age().equalTo(18).and().name().like("%rb%")) .run(); // SELECT * FROM `user` WHERE `user`.`id` = ? OR (`user`.`age` = ? AND `user`.`name` LIKE ?) List usersRun5_3 = UserQueryPro .selectBy().id().is().equalTo(1) .or().parLeft().age().equalTo(18).and().name().like("%rb%").parRight() .run(); ``` #### NOT运算符 ```java // 使用not查询 // SELECT * FROM `user` WHERE `user`.`id` <> ? List usersRun6_1 = UserQueryPro.selectBy().id().is().not().equalTo(2).run(); // SELECT * FROM `user` WHERE `user`.`id` not in (?, ?) List usersRun6_2 = UserQueryPro.selectBy().id().is().not().in(1, 2).run(); // SELECT * FROM `user` WHERE `user`.`id` not between (?, ?) List usersRun6_2 = UserQueryPro.selectBy().id().not().between(1, 2).run(); // SELECT * FROM `user` WHERE `user`.`name` NOT LIKE ? List usersRun6_2 = UserQueryPro.selectBy().name().not().like("%H%").run(); ``` #### ISNULL运算符 ```java // is null 查询 // SELECT * FROM `user` WHERE `user`.`age` IS NULL List users8_1 = UserQueryPro.selectBy().age().is().nul().run(); // SELECT * FROM `user` WHERE `user`.`age` IS NOT NULL List users8_2 = UserQueryPro.selectBy().age().is().not().nul().run(); // 忽略大小写 // SELECT * FROM `user` WHERE UPPER(`user`.`name`) LIKE UPPER(?) List users7 = UserQueryPro.selectBy().name().ignoreCase().like("%H%").run(); // 排序 // SELECT * FROM `user` ORDER BY `user`.`id` DESC List users10_1 = UserQueryPro.orderBy().id().desc().run(); // SELECT * FROM `user` ORDER BY `user`.`age` ASC, `user`.`id` DESC List users10_2 = UserQueryPro.orderBy().age().asc().id().desc().run(); // 限制返回结果数量 // SELECT * FROM `user` ORDER BY `user`.`age` DESC, `user`.`id` ASC LIMIT 1 List users11_1 = UserQueryPro.orderBy().age().desc().id().asc().limit(1).run(); // SELECT * FROM `user` LIMIT 1 User user11_2 = UserQueryPro.selectBy().runLimit1(); // 只需要返回部分字段 // SELECT `setting`.`id` FROM `setting` WHERE `setting`.`id` = ? List ids12_1 = SettingQueryPro.selectBy().id().equalTo(1).columnLimiter().id(); // SELECT `user`.`id`, `user`.`age` FROM `user` WHERE `user`.`id` = ? List users12_2 = UserQueryPro.selectBy().id().equalTo(1).columnsLimiter().id().age().run(); // SELECT `user`.`id`, `user`.`name` FROM `user` ORDER BY `user`.`age` DESC, `user`.`id` DESC LIMIT 1 List usersRun14 = UserQueryPro .orderBy().age().desc().id().desc().limit(1) .columnsLimiter().id().name() .run(); // take方法(方便写if等条件) // SELECT * FROM `user` WHERE `user`.`id` = ? AND `user`.`name` = ? List usersRun15 = UserQueryPro .selectBy().id().equalTo(1) .take(it -> True ? it.name().equalTo("hb") : it.name().equalTo("hb2")) .run(); // 自定义sql查询 // 待补充用例,customColumn+sql ``` ### 单表插入 支持批量插入,当数据量大于20条时,会自动启用`BigMode`, `BigMode`下,当预估sql大于0.5M(实际一般在2M以内, 取决于非ascii字符的数量以及参数的大小)或插入的行数大于1000,会自动分多次插入 ```java UserQueryPro.insert(...); // 参数支持 User, Map, Collection, vararg User ``` ### 单表删除 ```java // 逻辑删除默认启用,当存在deleted字段时,自动使用deleted字段进行逻辑删除 // UPDATE `setting` SET `deleted` = ? WHERE `setting`.`id` = ? SettingQueryPro.deleteBy().id().is().equalTo(1).run() // SELECT * FROM `setting` WHERE ( `setting`.`id` = ? OR `setting`.`kee` = ? ) AND `setting`.`deleted` = ? LIMIT 1 SettingQueryPro.selectBy().id().equalTo(1).or().kee().equalTo("lang").runLimit1() ``` #### 逻辑删除 逻辑删除默认就是启用行为。如果表字段存在`deleted`字段会使用逻辑删除。 ```java // 关闭逻辑删除 QueryProConfig.global.logicDelete(false); // 更改逻辑删除的字段,逻辑删除默认使用deleted字段,默认使用true代表逻辑已删除,false代表逻辑未删除 QueryProConfig.global.logicDelete(true, "removed", "Y", "N"); ``` ### 单表更新 ```java UserQueryPro.updateSet(new User(19)).where().id().equalTo(1).run(); UserQueryPro.updateSet().id(5).age(NULL).run(); ``` ### 直接运行SQL ```java // 单条查询语句 QueryProSql.create("SELECT * FROM user WHERE age > 500;").query(User.class); // 单条查询语句 文件模式 QueryProSql.create(Files.newInputStream(new File("temp.sql").toPath())).query(Setting.class); // 单条查询语句 查出单条数据 QueryProSql.create("SELECT * FROM user WHERE id = ?", 3).queryOne(); // 单条更新语句 QueryProSql.create("INSERT INTO user (id, name, age) VALUES (3, 'herb', 18)").exec(); // 动态插入, 会根据目标表结构自动过滤掉参数中多于键值 QueryProSql.create().insert("user", new HashMap() {{ put("name", "hb-new"); put("age", 8); put("key_will_be_ignored_when_table_column_not_exists", "ok"); }}); // 单条语句多参数(不够优雅,待更新) final List usersForBatchInsertByASqlAndMulParams = listOf( new User().setId(5L).setName("mul-hb").setAge(18), new User().setId(6L).setName("mul-hb").setAge(19), new User().setId(null).setName("mul-hb").setAge(20) ); QueryProSql.createBatch().add( "INSERT INTO user (id, name, age) VALUES (?, ?, ?)", usersForBatchInsertByASqlAndMulParams.stream().map(u -> new Object[]{u.getId(), u.getName(), u.getAge()}).collect(Collectors.toList()) ).exec(); // 多条语句(已有更好的方式,待更新) QueryProSession.useTransaction(() -> { QueryProSql.create( "drop temporary table if exists tmp;\n" + "create temporary table tmp select 1 as a, 'b' as b;" ).autoSplit().exec(); final Map row = QueryProSql.create("select * from tmp;").queryOne(); assertEquals(row, new HashMap() {{ put("a", 1); put("b", "b"); }}); QueryProSql.create("set @v1 = ?;set @v2 = ?;create temporary table tmp2 select @v1 as a, @v2 as b;", 1L, 2L).autoSplit().exec(); final Map row2 = QueryProSql.create("select * from tmp2;").queryOne(); assertEquals(row2, new HashMap() {{ put("a", 1L); put("b", 2L); }}); }); ``` ### 事务 `spring`环境下, 直接使用`@Transactional`或者`Spring`内置的手动管理事务的方法即可。 非`spring`环境下,使用 ```java QueryProSession.useTransaction(() -> { // 业务代码 return null; }) ``` ### 多表联合查询 ```java SettingQueryPro.plus() .leftJoin(User.class).on(Setting::getUserId, User::getId) .where().column(User::getName).eq("l") // 如果使用了任何非Setting和User下的字段都会在编译前报错 .and().column(Setting::getKee).eq("lang") .run(); ``` 更多用例见 [QueryProPlusTest](src/test/java/cn/cloudself/QueryProPlusTest.java) ### 配置项 #### 配置前必看 `QueryPro`的配置通过`QueryProConfig`进行。 `QueryProConfig`的配置有如下五个作用域: * `global`(全局) * `request`(请求): 这是基于`Spring web`的, 非`Spring`环境忽略该配置即可。 * `thread`(线程): 并不是很推荐使用该作用域。因为存在线程池,父子线程相关的问题,使用前需先阅读[1.线程级别的配置](#线程级别的配置), [2.父子线程场景](#配置在使用父子线程嵌套上下文等场景下的生效范围)。 * `context`(上下文) * `code`(针对某次查询) 这五个作用域的级别依次升高,意味着: `request`下的配置会在本次请求时"覆盖"掉`global`下的配置, `code`下的配置会覆盖掉其它所有级别的配置。 (可以放心的是,这种覆盖是隔离的,意味着针对日志的配置不会影响到针对数据源的配置) 举个例子: ```java // code 级别的配置,临时关闭逻辑删除 SettingQueryPro.logicDelete(false).deleteBy().id().equalTo(2).run(); // 上下文级别的配置,临时关闭逻辑删除 QueryProConfig.context.use(it -> { it.logicDelete(false); SettingQueryPro.deleteBy().id().equalTo(2).run(); }); // 仅在当前线程,关闭逻辑删除(注意可能存在线程池导致该配置失效) QueryProConfig.thread.logicDelete(false); // 仅在当前请求,临时关闭逻辑删除 QueryProConfig.request.logicDelete(false); // 关闭逻辑删除 QueryProConfig.global.logicDelete(false); // 仅在本次查询临时开启逻辑删除 SettingQueryPro.logicDelete(true).deleteBy().id().equalTo(2).run(); ``` #### 日志 ```java QueryProConfig.global .printLog(true) // 打印sql日志,默认开启 .beautifySql(true) // 美化sql(加入空格,换行等),默认开启 .printCallByInfo(true) // 打印调用QueryPro所在的代码行,默认开启 .printResult(true); // 打印返回结果,默认开启 // 另外 // 如下操作默认会在INFO级别关闭日志的打印,但是DEBUG级别还是会打印 QueryProConfig.global.printLog(false) // 关闭所有级别的日志打印,一般没必要这样做 QueryProConfig.global.printLog(false, LogLevel.DEBUG) ``` #### 数据源 如果你使用了`Spring`, `QueryPro`会自动装载由`Spring`管理的`DataSource`。 当然你也可以手动指定它。 ```java DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(drive); // 针对本次请求,临时切换数据源 QueryPro.request.dataSource(dataSource) // 针对某段查询,临时切换数据源 QueryProConfig.context.use((config) -> { config.dataSource(dataSource); User user = UserQueryPro.selectBy().name().equalTo("username").runLimit1(); List themes = SettingQueryPro.selectBy().kee().equalTo("theme").run(); }); // 针对某次查询,临时切换数据源 UserQueryPro.dataSource(dataSource).selectBy().name().equalTo("username").run(); ``` #### 返回Map时的字段类型映射 ##### 指定返回结果的类型 `JdbcQSR`会解析需返回结果的类型, 并尝试将`JDBC`的返回结果`ResultSet`转换成目标类型。 但是当无法解析出具体的字段类型时,例如:`runAsMap`方法,返回一个`Map`,此时便无法知晓需转换的具体类型是什么, 这时`JdbcQSR`会使用`JDBC`自带的`getObject`将返回结果转换成java类型,其行为参考下表,具体可至 `com.mysql.cj.jdbc.result.ResultSetImpl.getObject`查看 | SQL Type | Java Type | |--------------------|----------------------| | BIT | byte[], deserialized | | BOOLEAN | Boolean | | TINYINT | Integer | | TINYINT_UNSIGNED | Integer | | SMALLINT | Integer | | SMALLINT_UNSIGNED | Integer | | MEDIUMINT | Integer | | MEDIUMINT_UNSIGNED | Integer | | INT | Integer | | INT_UNSIGNED | Long | | BIGINT | Long | | BIGINT_UNSIGNED | BigInteger | | DECIMAL | BigDecimal | | DECIMAL_UNSIGNED | BigDecimal | | FLOAT | Float | | FLOAT_UNSIGNED | Float | | DOUBLE | Double | | DOUBLE_UNSIGNED | Double | | CHAR | String | | ENUM | String | | SET | String | | VARCHAR | String | | TINYTEXT | String | | TEXT | String | | MEDIUMTEXT | String | | LONGTEXT | String | | JSON | String | | GEOMETRY | byte[] | | BINARY | byte[], deserialized | | VARBINARY | byte[], deserialized | | TINYBLOB | byte[], deserialized | | MEDIUMBLOB | byte[], deserialized | | LONGBLOB | byte[], deserialized | | BLOB | byte[], deserialized | | YEAR | Date, Short | | DATE | Date | | TIME | Time | | TIMESTAMP | Timestamp | | DATETIME | LocalDateTime | | - | String | 可以看到它会将无符号的长整型`BIGINT_UNSIGNED`类型转为`BigInteger`。 但是针对`id`字段,通常我们希望转为`Long`类型, 就可以使用如下代码配置一个dbColumn解析器。 ```java // 如果没有指定返回结果的类型,将id或_id结尾的无符号BIGINT类型转为Long。(这是默认行为,无需额外配置) QueryProConfig.global.dbColumnInfoToJavaType().put( (columnInfo) -> { if (columnInfo.getType().startsWith("BIGINT")) { // 以BIGINT开头,包括了BIGINT_UNSIGNED String label = columnInfo.getLabel(); if (label.equals("id") || label.endsWith("_id")) { // 字段名为id或者以_id结尾的 return true; } } return false; }, Long.class // 转为Long类型 ); ``` ##### 增加某种返回类型的支持 上面说到:`JdbcQSR`会解析需返回结果的类型, 并尝试将`JDBC`的返回结果`ResultSet`转换成目标类型。 这是通过配置中的`ResultSetParsers`实现的,我们可以通过`QueryProConfig.global.addResultSetParser()`添加它。 默认支持的类型有`BigDecimal`, `Byte`, `ByteArray`, `Date`, `LocalDate`, `LocalTime`, `LocalDateTime`, `java.sql.Date`, `Double`, `Float`, `Int`, `Long`, `Time`, `Timestamp`, `Short`, `String` 以及 `枚举类型`。 ```java // 例如,这两个ResultSetParser一个简单一个略复杂,都已经内置在了`QueryPro`中 QueryProConfig.global .addResultSetParser( LocalDateTime.class, (resultSet, columIndex) -> resultSet.getTimestamp(columIndex).toLocalDateTime() ) .addResultSetParserEx((resultSet, targetClass, columnIndex) -> { // 当需要同时支持多种返回类型时,可以使用addResultSetParserEx if (!targetClass.isEnum()) { return Optional.empty(); } return Optional.of(Enum.valueOf((Class) targetClass, resultSet.getString(columnIndex))); }); ``` #### 其它 ##### 设置需要忽略字段 例如,忽略`serialVersionUID`这个字段 (这是默认行为) ```java QueryProConfig.global.shouldIgnoreFields().add("serialVersionUID"); ``` ##### 自定义QueryStructure解析器 得益于良好的分层结构 `QueryPro`在处理`QueryStructure`转返回结果时,默认实现并使用了`JdbcQSR`, 它使用Jdbc进行查询,不依赖`Mybatis`, `Spring Data`等框架。 出于某些目的,你也可以替换它。 ```java QueryProConfig.global.setQueryStructureResolver(new JdbcQSR()); ``` #### 生命周期 ```java QueryProConfig.global .lifecycle() .beforeInsert( builder -> builder .addField("deleted", Boolean.class, () -> false) .addField("create_time", Date.class, Date::new) .addField("create_by", String.class, XX::getCurrentUser) ) .beforeUpdate( builder -> builder .overrideField("update_time", Date.class, Date::new) .overrideField("update_by", String.class, XX::getCurrentUser) ); ``` * **`.selectBy()...`, `.updateSet()...`, `.deleteBy()...`, `(QueryPro).insert...`的生命周期:** * ``` * [beforeExec, beforeSelect](取决于先调用哪个) -> * [beforeRunSql, runSqlAround] -> * [afterRunSql, runSqlAround] -> * [afterExec, afterSelect] -> * return result * ``` *
* **`QueryProSql...`的生命周期** * ``` * [beforeRunSql, runSqlAround] -> * [afterRunSql, runSqlAround] -> * return result #### 线程级别的配置 #### 配置在:使用父子线程,嵌套上下文等场景下的生效范围 1. 同时使用父子线程时,如果子线程没有使用线程池,上下文配置(`context`)和线程配置(`thread`)在父子线程中都是完全共享的 如果子线程使用了线程池,(包括`Stream.parcel()`等),需注意在子线程中需要手动传递上下文信息。 ```java // 未使用线程池的例子 // 尽管`context`作用域是基于ThreadLocal实现的,但它在同时使用父子线程时,配置在上下文中还是可以完全共享的。 QueryProConfig.context.use(it -> { Assertions.assertTrue(QueryProConfig.computed.logicDelete()); it.logicDelete(false); Assertions.assertFalse(QueryProConfig.computed.logicDelete()); Assertions.assertFalse(QueryProConfig.computed.printLargeElementWholly()); Thread thread = new Thread(() -> { // 子线程能正常读取父线程的配置 Assertions.assertFalse(QueryProConfig.computed.logicDelete()); // 子线程产生的配置会被父线程读到 it.logicDelete(true); Assertions.assertTrue(QueryProConfig.computed.logicDelete()); // 子线程初始化后,父线程产生的配置依然可以被子线程读取 try { Thread.sleep(100L); } catch (InterruptedException e) { throw new RuntimeException(e); } Assertions.assertTrue(QueryProConfig.computed.printLargeElementWholly()); }); // 创建子线程,该动作会把配置共享给子线程 thread.start(); // 子线程初始化后,父线程产生的配置依然可以被子线程读取 it.printLargeElementWholly(true); Assertions.assertTrue(QueryProConfig.computed.printLargeElementWholly()); thread.join(); // 子线程产生的配置会被父线程读到 Assertions.assertTrue(QueryProConfig.computed.logicDelete()); }); // 使用线程池的例子 QueryProConfig.context.use(it -> { // it. ... IntStream.range(0, 100).parallel().peek(i -> { // 这行是必须的,否则配置会错乱 it.linkToCurrentThread(); }); }); ``` 2. 嵌套的上下文,配置信息完全独立 ```java QueryProConfig.context.use(it -> { Assertions.assertTrue(QueryProConfig.computed.logicDelete()); it.logicDelete(false); Assertions.assertFalse(QueryProConfig.computed.logicDelete()); Assertions.assertTrue(QueryProConfig.computed.beautifySql()); QueryProConfig.context.use(nested -> { // 独立 Assertions.assertTrue(QueryProConfig.computed.logicDelete()); // 独立 Assertions.assertTrue(QueryProConfig.computed.beautifySql()); nested.beautifySql(false); Assertions.assertFalse(QueryProConfig.computed.beautifySql()); }); // 独立 Assertions.assertTrue(QueryProConfig.computed.beautifySql()); }); ``` ### 直接访问ResultSet ```java public class ResultSetWalkerTest { private static class ResultSetWalker implements JdbcQSR.IResultSetWalker { final private List columnNames = new ArrayList<>(); @Override public void walk(@NotNull ResultSet rs) throws Exception { final ResultSetMetaData metaData = rs.getMetaData(); final int columnCount = metaData.getColumnCount(); for (int i = 1; i <= columnCount; i++) { final String columnLabel = metaData.getColumnLabel(i); columnNames.add(columnLabel); } } } @Test public void test() { QueryProConfig.context.use(conf -> { // 测试用例中,需要初始化QueryPro initLogger(); conf.dataSource(getDataSource()); // 提供一个实现了JdbcQSR.IResultSetWalker的类 final ResultSetWalker walker = QueryProSql.create("select * from user left join setting on user.id = setting.user_id").queryOne(ResultSetWalker.class); assert walker != null; System.out.println(walker.columnNames); });} } ``` ### 源码结构及代码运行流程 #### 代码运行流程 总的来说,`QueryPro`的运作流程分为两部分。 1. 在调用`run`之前,`QueryPro`的所有代码都是为了合理的生成并修改`QueryStructure`这个结构, 2. 调用`run`之后, 通过`ToSqlByQueryStructure`将`QueryStructure`转成目标sql, 然后通过`JdbcQSR`执行sql并生成目标对象返回。 整个[resolver包](src%2Fmain%2Fjava%2Fcn%2Fcloudself%2Fquery%2Fresolver%2Fpackage-info.java))就是处理调用`run`之后的事情。 ``` QueryStructure的设计哲学: 易于序列化, 以便多端生成,并传输 ``` #### 源码结构 * [config包](src%2Fmain%2Fkotlin%2Fcn%2Fcloudself%2Fquery%2Fconfig%2Fpackage-info.md) `QueryPro`支持很多不同级别的配置(`全局`、`请求`、`线程`、`上下文`以及`单次查询`)该包主要用于处理这些配置信息。 * [resolver包](src%2Fmain%2Fkotlin%2Fcn%2Fcloudself%2Fquery%2Fresolver%2Fpackage-info.md) 将`QueryStructure`等结构转换为`SQL`并执行或直接执行`SQL` * [QueryStructure.kt](src%2Fmain%2Fkotlin%2Fcn%2Fcloudself%2Fquery%2FQueryStructure.kt) 描述查询,更新,插入,删除的一个可序列化的结构 * [QueryPayload.kt](src%2Fmain%2Fkotlin%2Fcn%2Fcloudself%2Fquery%2FQueryPayload.kt) `QueryStructure`是为了生成`SQL`, `QueryPayload`是为了记录一些查询时产生的非`SQL`组成部分的配置,有了这两个,就可以进行[resolve](src%2Fmain%2Fkotlin%2Fcn%2Fcloudself%2Fquery%2Fresolver%2Fpackage-info.md)了,`QueryPayload`包含单次查询中编写的配置信息(如有),查询的元信息等 * [QueryPro.java](src%2Fmain%2Fkotlin%2Fcn%2Fcloudself%2Fquery%2FQueryPro.kt) 查询(也包含增删改)入口,所有生成的`QueryPro`文件均`'继承'`(实际上是委托(`delegate`))该类 * [QueryProSql.java](src%2Fmain%2Fkotlin%2Fcn%2Fcloudself%2Fquery%2FQueryProSql.kt) 直接执行`SQL`语句或者动态生成插入语句并执行 * plus包 额外添加多表联合查询的支持 * util包 一些工具类 * [Pageable.kt](src%2Fmain%2Fkotlin%2Fcn%2Fcloudself%2Fquery%2FPageable.kt) 为查询添加分页的支持(含两种模式:总条数模式和是否存在下一页模式) ### 后续规划 类似这样的语句的处理 UPDATE word SET score = score + 1 WHERE id = 1 对sum, concat, group_concat, discount等 的支持 添加带下划线驼峰式的列(更新操作) 添加ENUM的支持 UPDATE 需要使用class结构生成SQL UPDATE 时 非逻辑删除BUG query pro 生成文件添加 local date 以及未知类型 的支持 Session 和 Transaction 的嵌套问题