Db 工具类
基础使用
1. 常见用法
Db
类及其配套的 Row
类,提供了在 Model
类之外更为丰富的数据库操作功能。使用 Db
与 Row
类时,无需对数据库表进行映射,Row
相当于一个通用的 Model
。以下是 Db + Row
模式的一些常见用法:
导入配置
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Row;
import com.litongjava.model.db.IAtom;
// 创建 name 属性为 James, age 属性为 25 的 record 对象并添加到数据库
Row user = new Row().set("name", "James").set("age", 25);
Db.save("user", user);
// 删除 id 值为 25 的 user 表中的记录
Db.deleteById("user", 25);
// 查询 id 值为 25 的 Record,将其 name 属性改为 James 并更新到数据库
user = Db.findById("user", 25).set("name", "James");
Db.update("user", user);
// 获取 user 的 name 属性
String userName = user.getStr("name");
// 获取 user 的 age 属性
Integer userAge = user.getInt("age");
// 查询所有年龄大于 18 岁的用户
List<Row> users = Db.find("select * from user where age > 18");
// 分页查询年龄大于 18 岁的用户,当前页号为 1,每页 10 个用户
Page<Row> userPage = Db.paginate(1, 10, "select *", "from user where age > ?", 18);
2. 事务处理示例
以下示例展示了如何在事务中执行多个数据库操作:
boolean succeed = Db.tx(new IAtom(){
public boolean run() throws SQLException {
int count = Db.updateBySql("update account set cash = cash - ? where id = ?", 100, 123);
int count2 = Db.updateBySql("update account set cash = cash + ? where id = ?", 100, 456);
return count == 1 && count2 == 1;
}
});
以上两次数据库更新操作在一个事务中执行。如果执行过程中发生异常或 run()
方法返回 false
,则事务会自动回滚。
3. Db.query 方法
第一种用法
当 SELECT
后的字段只有一个时,可以使用合适的泛型接收数据:
List<String> titleList = Db.query("select title from blog");
以上 SQL 中 SELECT
后只有一个 title
字段,因此使用 List<String>
来接收数据。接收数据的泛型变量可根据返回值类型变化,例如:
List<Integer> idList = Db.query("select id from blog");
在此例中,id
字段的返回值为 Integer
,因此接收变量为 List<Integer>
。
第二种用法
当 SELECT
后的字段有多个时,必须使用 List<Object[]>
接收数据,例如:
List<Object[]> list = Db.query("select id, title, content from blog");
List<Object[]> list = Db.query("select * from blog");
4. Db.queryXxx 方法
Db.queryXxx
系列方法包括 queryInt
、queryLong
、queryStr
等,对于使用聚合函数的 SQL 十分方便,例如:
int total = Db.queryInt("select count(*) from account");
以上 SQL 使用了 count(*)
聚合函数,使用 Db.queryInt
不仅方便,而且性能最佳。
此外,还可以用于查询某条记录的某个字段值,例如:
String nickName = Db.queryStr("select nickName from account where id = ? limit 1", 123);
上述代码通过 queryStr
方便地查询了 id
值为 123
的 account
的 nickName
。
需要注意,Db.queryXxx
系列方法要求 SELECT
后只能有一个字段名,或者说只能返回一个列值(例如 count(*)
)。
5. Db.find 与 Db.query 系列方法的区别
- Db.find 系列方法:将返回值一律封装到一个
Row
对象中。 - Db.query 系列方法:不进行封装,直接将数据原样返回。
两者在查询所使用的 SQL 及参数用法上完全相同。
6. updateBySql
如果你编写了 sql 语句,需要进行插入,更新获取删除操作,请使用 updateBySql 方法,默认的 update 方法不支持传入 sql 使用示例
String updateBalanceBind2 = "UPDATE users SET balance = balance + 0.025, bind2_count = bind2_count + 1 WHERE tg_id = ?";
Db.updateBySql(updateBalanceBind2, bind2Id);
7. 扩展 Db 功能
Db
工具类的所有功能依赖于底层的 DbPro
。可以通过继承 DbPro
来定制所需的功能。例如:
public class MyDbPro extends DbPro {
public MyDbPro(String configName) {
super(configName);
}
@Override
public List<Row> find(String sql, Object... paras) {
System.out.println("Sql: " + sql);
System.out.println("Paras: " + Arrays.toString(paras));
return super.find(sql, paras);
}
}
以上代码扩展了 DbPro
并覆盖了父类的 find(String, Object...)
方法,该方法在调用 super.find(...)
之前输出了 SQL 及其参数值。
然后,通过配置让 MyDbPro
取代 DbPro
的功能:
ActiveRecordPlugin arp = new ActiveRecordPlugin(...);
arp.setDbProFactory(configName -> new MyDbPro(configName));
通过上述配置,在使用 Db.find(String, Object...)
方法时,将使用自定义的 MyDbPro
中实现的 find
方法。这种方法可以替换、增强或改变所有 DbPro
中 public
和 protected
方法的行为,极为灵活方便。
分页
1. 常用 paginate 方法
Model
与 Db
中提供了最常用的分页 API:
paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras)
参数含义:
pageNumber
:当前页的页号pageSize
:每页数据条数select
:SQL 语句的SELECT
部分sqlExceptSelect
:SQL 语句除了SELECT
以外的部分paras
:查询参数
绝大多数情况下,使用此 API 即可。以下是使用示例:
dao.paginate(1, 10, "select *", "from girl where age > ? and weight < ?", 18, 50);
2. 带有 Group By 的 paginate
API 原型:
paginate(int pageNumber, int pageSize, boolean isGroupBySql, String select, String sqlExceptSelect, Object... paras)
相对于第一种,仅多了一个 boolean isGroupBySql
参数。以下是代码示例:
dao.paginate(1, 10, true, "select *", "from girl where age > ? group by age", 18);
在以上代码中,SQL 的最外层包含 GROUP BY age
,因此第三个参数 isGroupBySql
需传入 true
。如果是嵌套 SQL,但 GROUP BY
不在最外层,则第三个参数必须为 false
,例如:
select * from (select x from t group by y) as temp
重点提示:
isGroupBySql
参数仅在最外层 SQL 包含GROUP BY
子句时设为true
。- 如果
GROUP BY
仅存在于嵌套 SQL 的内层,则应设为false
。
3. 使用 paginateByFullSql 方法
API 原型:
paginateByFullSql(int pageNumber, int pageSize, String totalRowSql, String findSql, Object... paras)
相对于其他 paginate
API,将查询总行数与查询数据的两条 SQL 独立出来。这主要用于应对复杂的 ORDER BY
语句或 SELECT
中带有 DISTINCT
的情况。当使用第一种 paginate
方法出现异常时,可考虑使用此 API。以下是代码示例:
String from = "from girl where age > ?";
String totalRowSql = "select count(*) " + from;
String findSql = "select * " + from + " order by age";
dao.paginateByFullSql(1, 10, totalRowSql, findSql, 18);
在上述代码中,ORDER BY
子句并不复杂,因此使用第一种 paginate
方法即可。
重点:
totalRowSql
与findSql
必须共用最后一个参数paras
,即:dao.find(totalRowSql, paras)
dao.find(findSql, paras)
这两条 SQL 都必须能够正确执行,否则无法使用
paginateByFullSql
。
4. 使用 SqlPara 参数的 paginate
API 原型:
paginate(int pageNumber, int pageSize, SqlPara sqlPara)
此方法用于配合 SQL 管理功能使用,具体将在 SQL 管理功能章节中详细介绍。
5. 常见问题及解决方案
为了有效解决分页过程中可能遇到的问题,需了解 paginate
方法的底层实现原理。以下假设有如下分页代码:
paginate(1, 5, "select *", "from article where id > ? order by id", 10);
底层实现:
生成用于获取满足查询条件的所有记录数(
totalRow
)的 SQL:select count(*) from article where id > 10
"select count(*)"
是固定写死的。"from article where id > 10"
是根据用户提供的第四个参数,去除ORDER BY id
后得到的。
去除
ORDER BY
子句的原因:- 许多数据库不支持带有
ORDER BY
子句的SELECT count(*)
查询,必须去除以避免错误。 SELECT count(*)
查询在有无ORDER BY
子句时结果相同,去除后有助于提升性能。
- 许多数据库不支持带有
常见错误及解决方案:
第一类错误:如果用户分页的第四个参数中含有多个问号占位符,可能导致参数错位。
示例:
paginate(1, 5, "select (... where x = ?)", "from article where id = ?", 8, 10);
此时生成的用于计算 totalRow
的 SQL 为:
select count(*) from article where id = ?, 8, 10
由于多了一个参数 8
,会导致 SQL 执行异常。解决方法是将原 SQL 外层套一个 SQL,使第三个参数中不含问号占位符:
paginate(1, 5, "select *", "from (" + 原sql + ") as t", 8, 10);
即将原 SQL 外层再套一个 SELECT _ FROM (...) as t
,避免 count(*)
查询时参数错位。
第二类错误:如果 ORDER BY
子句使用了子查询或函数调用,paginate
方法可能无法正确移除 ORDER BY
子句,导致 SQL 错误。
示例:
paginate(1, 5, "select *", "from ... order by concat(...)", ...);
解决方法:使用 paginateByFullSql
方法,让用户手动编写用于计算 totalRow
的 SQL 与查询数据的 SQL,避免 paginate
方法自动处理 ORDER BY
子句带来的问题。
总结:
- 了解
paginate
的底层工作机制有助于有效解决分页过程中遇到的问题。 - 在复杂 SQL 情况下,优先考虑使用
paginateByFullSql
方法以获得更大的灵活性。
通过以上内容,您可以全面掌握 Db
工具类的使用方法及其分页功能,并有效应对常见问题,提升数据库操作的效率与稳定性。