Fastquery
FastQuery(Method of fast database query) 基于Java语言. 简化Java操作数据层.做为一个开发者,仅仅只需要设计编写DAO接口. 因此,开发代码不得不简洁而优雅.从而,大幅度提升开发效率.
Install / Use
/learn @xixifeng/FastqueryREADME
Apache Maven
<dependency>
<groupId>org.fastquery</groupId>
<artifactId>fastquery</artifactId>
<version>1.0.140</version> <!-- fastquery.version -->
</dependency>
Gradle/Grails
compile 'org.fastquery:fastquery:1.0.140'
FastQuery 数据持久层框架
FastQuery 基于Java语言.简化Java操作数据层.<br />
提供少许Annotation,消费者只用关心注解的含义,这就使得框架的核心便于重构,便于持续良性发展.<br />
FastQuery 主要特性如下:
- 遵循非侵入式原则,设计优雅或简单,极易上手
- 在项目初始化阶段采用生成好字节码,因此支持编译前预处理,可最大限度减少运行期的错误,显著提升程序的强壮性
- 支持安全查询,防止SQL注入
- 支持与主流数据库连接池框架集成
- 支持
@Query查询,使用@Condition,可实现动态where条件查询 - 支持查询结果集以JSON类型返回
- 拥有非常优雅的
Page(分页)设计 - 使用
@Source可实现动态适配数据源.这个特性特别适合多租户系统中要求数据库彼此隔离其结构相同的场景里 - 支持
@QueryByNamed命名式查询,SQL动态模板 - 支持存储过程
- 支持批量更新集合实体(根据主键,批量更新不同字段,不同内容).
运行环境要求
JRE 8+
配置文件
配置文件的存放位置
默认从classpath目录下去寻找配置文件. 配置文件的存放位置支持自定义, 如: System.setProperty("fastquery.config.dir","/data/fastquery/configs");, 它将会覆盖classpath目录里的同名配置文件. 如果项目是以jar包的形式启动,那么可以通过java命令的 -D 参数指定配置文件的目录, 如: java -jar Start.jar -Dfastquery.config.dir=/data/fastquery/configs.
c3p0-config.xml
完全支持c3p0官方配置,详情配置请参照c3p0官网的说明.
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="xk-c3p0">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://192.168.1.1:3306/xk?useSSL=false</property>
<property name="user">xk</property>
<property name="password">abc123</property>
<property name="acquireIncrement">50</property>
<property name="initialPoolSize">100</property>
<property name="minPoolSize">50</property>
<property name="maxPoolSize">1000</property>
<property name="maxStatements">0</property>
<property name="maxStatementsPerConnection">5</property>
</named-config>
<!-- 可以配置多个named-config节点,多个数据源 -->
<named-config name="name-x"> ... ... </named-config>
</c3p0-config>
druid.xml
用于配置支持Druid连接池,详细配置请参照 https://github.com/alibaba/druid
<beans>
<bean name="xkdb1" id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://db.fastquery.org:3305/xk" />
<property name="username" value="xk" />
<property name="password" value="abc123" />
<property name="filters" value="stat" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
</bean>
<!-- 再配置一个数据源 -->
<bean name="xkdb2" id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://db.fastquery.org:3305/xk" />
<property name="username" value="xk" />
<property name="password" value="abc123" />
</bean>
</beans>
hikari.xml
用于配置支持HikariCP连接池,详细配置选项请参照 https://github.com/brettwooldridge/HikariCP
连接MySQL,为了得到更好的性能,推荐配置
<beans>
<bean name="xkdb2">
<property name="jdbcUrl" value="jdbc:mysql://192.168.1.1:3306/xk" />
<property name="dataSource.user" value="xk" />
<property name="dataSource.password" value="abc123" />
<property name="dataSource.cachePrepStmts" value="true" />
<property name="dataSource.prepStmtCacheSize" value="250" />
<property name="dataSource.prepStmtCacheSqlLimit" value="2048" />
<property name="dataSource.useServerPrepStmts" value="true" />
<property name="dataSource.useLocalSessionState" value="true" />
<property name="dataSource.rewriteBatchedStatements" value="false" />
<property name="dataSource.cacheResultSetMetadata" value="true" />
<property name="dataSource.cacheServerConfiguration" value="true" />
<property name="dataSource.elideSetAutoCommits" value="true" />
<property name="dataSource.maintainTimeStats" value="false" />
</bean>
<!-- 可以配置多个bean节点,提供多个数据源 -->
<bean name="name-x"> ... ... </bean>
</beans>
支持多连接池共存,如,同时让Druid,HikariCP工作,并配置多个数据源.
fastquery.json
配置数据源的作用范围
// @author xixifeng (fastquery@126.com)
// 配置必须遵循标准的json语法.
{
"scope":[
// config 用于指定由谁来提供数据源,如,"c3p0","druid","hikari"等等
{
"config": "c3p0", // 表示由c3p0负责提供数据源
"dataSourceName": "xk-c3p0", // 数据源的名称
"basePackages": [ // 该数据源的作用范围
"org.fastquery.example", // 包地址
"org.fastquery.dao.UserInfoDBService" // 完整类名称
// 在这可以配置多个DB接口或包地址,以","号隔开
// 提醒:在json结构中,数组的最后一个元素的后面不能加","
]
},
/*
再配置一个数据源作用域
*/
{
"config" : "mySQLDriver", // 表示由mySQLDriver负责提供数据源
"dataSourceName": "shtest_db", // 数据源的名称
"basePackages": [ // 该数据源的作用范围
"org.fastquery.example.DataAcquireDbService"
// 在这可以配置多个DB接口,以","号隔开
]
},
{
"config": "c3p0", // 表示由c3p0负责提供数据源
"basePackages": [
"org.fastquery.dao2.UserInfoDB"
]
}
]
}
注意: 在fastquery.json中配置作用域,其中"dataSourceName"不是必须的,"dataSourceName"要么不指定,要指定的话那么必须正确.如果没有指定"dataSourceName",那么在调用接口的时候必须指定数据源的名称.下面的适配数据源章节会讲到."basePackages"若配置了包地址,那么对应的数据源会作用这个包的所有类,及所有子包中的类.
数据源的初始化是从"fastquery.json"开始的,根据从里面读到"dataSourceName"的值,取相应的配置,继而完成数据源的创建.如,创建一个名为"rex-db"的数据源:
{
"config": "c3p0",
"dataSourceName": "rex-db"
}
在这里,"basePackages"不是必须的,该数据源可以当做是一个服务,供没有明确指定数据源的Repository使用.
入门例子
当看到一个例子时,切勿断章取义,多看一眼,往往会有意想不到的结果.
- 准备一个实体
public class Student
{
private String no;
private String name;
private String sex;
private Integer age;
private String dept;
// getter / setter 省略...
}
实体属性跟数据库映射的字段必须为包装类型,否则被忽略. 在实体属性上标识@Transient,表示该字段不参与映射.
- DAO接口
public interface StudentDBService extends org.fastquery.core.Repository {
@Query("select no, name, sex from student")
JSONArray findAll();
@Query("select no,name,sex,age,dept from student")
Student[] find();
}
- 使用DAO接口.
public class StudentDBServiceTest {
// 获取实现类
private static StudentDBService studentDBService = FQuery.getRepository(StudentDBService.class);
@Test
public void test() {
// 调用 findAll 方法
JSONArray jsonArray = studentDBService.findAll();
// 调用 find 方法
Student[] students = studentDBService.find();
}
}
注意:不用去实现StudentDBService接口.通过FQuery.getRepository获取DAO接口对应的实例,虽然每次获取实例消耗的性能微乎其微可以忽略不计,但是,作为一个接口并且频繁被调用,因此,建议把获取到的实例赋值给类成员变量,最好是用static修饰.FQuery.getRepository获得的实例是唯一的,不可变的.
一个接口不实现它的public abstract方法就毫无作用可言,因此,与之对应的实例对象是必须的,只不过是FastQuery内部替用户实现了.读者可能会问,这个自动生成的实例在什么时候生成? 动态生成的效率如何保持高效? 为此, 笔者做了相当多的功课:让所有DB实现类在项目初始化阶段进行,并且尽可能地对接口方法做静态分析,把有可能在运行期发生的错误尽最大努力提升到初始化阶段,生成代码前会检测SQL绑定是否合法有效、检测方法返回值是否符合常规、方法的参数是否满足模版的调用、是否正确地使用了分页...诸如此类问题.这些潜在问题一旦暴露,项目都启动不起来,错误信息将在开发阶段详细输出,并且必须干掉这些本该在生产环境才发生的错误,才能继续开发,迫使开发者必须朝正确的道路走,或者说框架的优良设计其核心理念引导开发者不得不写出稳健的程式.项目进入运行期,大量的校验就没必要写了,从而最大限度保证快速执行.
唯一的出路,只能引用接口,这就使得开发者编程起来不得不简单,因为面对的是一个高度抽象的模型,而不必去考虑细枝末节.接口可以看成是一个能解析SQL并能自动执行的模型,方法的参数、绑定的模版和标识的注解无不是为了实现一个目的:执行SQL,返回结果.
这种不得不面向接口的编程风格,有很多好处:耦合度趋向0,天然就是对修改封闭,对扩展开放,不管是应用层维护还是对框架增加新特性,这些都变得特别容易.隐藏实现,可以减少bug或者是能消灭bug,就如解决问题,不如消灭问题一般,解决问题的造诣远远落后于消灭问题,原因在于问题被解决后,不能证明另一个潜在问题在解决代码中不再出现,显然消灭问题更胜一筹.应用层只用写声明抽象方法和标识注解,试问bug从何而来?该框架最大的优良之处就是让开发者没办法去制造bug,至少说很难搞出问题来.不得不简便,没法造bug,显然是该项目所追求的核心目标之一.
不管用不用这个项目,笔者期望读者至少能快速地检阅一下该文档,有很多设计是众多同类框架所不具备的,希望读者从中得到正面启发或反面启发,哪怕一点点,都会使你收益.
针对本文@Query的由来
该项目开源后,有些习惯于繁杂编码的开发者表示,"使用@Query语义不强,为何不用@SQL,@Select,@Insert,@Update...?". SQL的全称是 Structured Query Language,本文的 @Query 就是来源于此. @Query只作为运行SQL的载体,要做什么事情由SQL自己决定.因此,不要片面的认为Query就是select操作. 针对数据库操作的注解没有必要根据SQL的四种语言(DDL,DML,DCL,TCL)来定义,定义太多,只会增加复杂度,并且毫无必要,如果是改操作加上@Modifying注解,反之,都是"查",这样不更简洁实用吗? 诸如此类:@Insert("insert into table (name) values('Sir.Xi')"),@Select("select * from table"),SQL的表达能力还不够吗? 就不觉得多出@insert和@Select有拖泥带水之嫌? SQL的语义本身就很强,甚至连@Query和@Modifying都略显多余,但是毕竟SQL需要有一个载体和一个大致的分类.
带条件查询
// sql中的?1 表示对应当前方法的第1个参数
// sql中的?2 表示对应当前方法的第2个参数
// ?N 表示对应当前方法的第N个参数
// 查询返回数组格式
@Query("select no,name,sex,age,dept from student s where s.sex=:sex and s.age > ?1")
Student[] find(Integer age,@Param("sex")String sex);
// 查询返回JSON格式
@Query("select no, name, sex from student s where s.sex=:sex and s.age > ?2")
JSONArray find(@Param("sex")String sex,Integer age);
// 查询返回List Map
@Query("select no, name, sex from student s where s.sex=?1 and s.age > :age")
List<Map<String, Object>> findBy(String sex,@Param("age")Integer age);
// 查询返回List 实体
@Query("select id,name,age from `userinfo` as u where u.id>?1")
List<UserInfo> findSome(@Param("id")Integer id);
参数较多时不建议使用问号(?)引用参数,因为它跟方法的参数顺序有关,不便维护,可以使用冒号(:)表达式,跟顺序无关, ":name" 表示引用标记有@Param("name")的那个参数.
若返回List<Map<String, String>>或Map<String, String>,会把查询出的字段值(value)包装成字符串.
注意: 在没有查询到数据的情况下,如果返回值是集合类型或JSON类型或者是数组类型,返回具体的值不会是null,而是一个空对象(empty object)集合或空对象JSON或者是长度为0的数组.
使用空对象来代替返回null,它与有意义的对象一样,并且能避免NullPointerException,阻止null肆意传播,可以减少运行期错误.反对者一般都从性能的角度来考虑,认为new一个空对象替代null,会增加系统的开销.可是,<<Effective Java>>的作者Josh Bloch说,在这个级别上担心性能问题是不明智的,除非有分析表明,返回空对象来替代返回null正是造成性能问题的源头.细心的人可能已经发现JDK新版本的API都在努力避免返回null.
举例说明:
// 针对该方法,如果没有查询到数据,返回
Related Skills
oracle
353.1kBest practices for using the oracle CLI (prompt + file bundling, engines, sessions, and file attachment patterns).
prose
353.1kOpenProse VM skill pack. Activate on any `prose` command, .prose files, or OpenProse mentions; orchestrates multi-agent workflows.
Command Development
111.6kThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
Plugin Structure
111.6kThis skill should be used when the user asks to "create a plugin", "scaffold a plugin", "understand plugin structure", "organize plugin components", "set up plugin.json", "use ${CLAUDE_PLUGIN_ROOT}", "add commands/agents/skills/hooks", "configure auto-discovery", or needs guidance on plugin directory layout, manifest configuration, component organization, file naming conventions, or Claude Code plugin architecture best practices.
