初始化(可选)
零配置启动(默认行为)
// 无需任何初始化——直接使用即可。
// 首次操作时自动以 H2 内存数据库启动,并自动建表(autoCreateTable=true)。
// 同时会打印 WARN 日志提示当前为默认模式,生产环境应显式初始化。
Vostok.Data.insert(user);
List<User> users = Vostok.Data.findAll(User.class);
注意
默认自动初始化使用 H2 内存数据库(
jdbc:h2:mem:vostok_default;MODE=MySQL),数据在进程退出后丢失,仅适用于快速原型和测试场景。
生产环境请务必显式调用 init() 配置目标数据源。
显式初始化(生产环境推荐)
Vostok.Data.init(
new VKDataConfig()
.url("jdbc:mysql://127.0.0.1:3306/mydb")
.username("root")
.password("123456")
.driver("com.mysql.cj.jdbc.Driver")
.maxActive(20)
.minIdle(5),
"com.example.entity" // 实体扫描包,可传多个
);
说明
支持 MySQL、PostgreSQL、Oracle、DB2、SQL Server,框架通过
dialect 字段自动适配分页等方言差异(也可显式指定
VKDialectType)。
实体定义
@VKEntity(table = "users")
public class User {
@VKId(auto = true) // auto=true:数据库自增主键,INSERT 时不包含该字段
private Long id;
private String name;
@VKColumn(name = "email_addr", nullable = false, length = 100, unique = true) // 非空、长度、唯一约束(自动建表生效)
private String email;
@VKColumn(name = "phone", encrypted = true) // 开启字段透明加密
private String phone;
@VKColumn(name = "created_at", updatable = false) // 只写入 INSERT,UPDATE 中忽略
private String createdAt;
@VKVersion // 乐观锁版本字段,框架自动维护递增与冲突检测
@VKColumn(name = "version")
private Long version;
@VKLogicDelete(deletedValue = "1", normalValue = "0") // 逻辑删除标志
@VKColumn(name = "is_deleted")
private Integer isDeleted;
@VKIgnore // 标记该字段不参与持久化
private String tempToken;
// getters / setters
}
实体注解说明
| 注解 | 作用范围 | 说明 |
| @VKEntity(table) | 类 | 标记持久化实体,table 指定表名(默认类名转下划线) |
| @VKId(auto) | 字段 | 标记主键字段;auto=true 时 INSERT 不写入该列并回填生成的主键 |
| @VKColumn(name, encrypted, keyId, insertable, updatable, nullable, length, unique) | 字段 | 自定义列名;encrypted=true 开启透明加密;insertable=false 排除 INSERT;updatable=false 排除 UPDATE SET 子句;nullable=false 自动建表生成 NOT NULL;length 指定 VARCHAR 长度(默认 255);unique=true 自动建表生成 UNIQUE 约束 |
| @VKVersion | 字段 | 乐观锁版本字段(Long/Integer);INSERT 初始化为 0;UPDATE 自动 version+1 并校验旧版本,冲突抛 VKOptimisticLockException |
| @VKLogicDelete(deletedValue, normalValue) | 字段 | 逻辑删除标志字段;DELETE 转为软删 UPDATE;所有查询自动注入 normalValue 过滤 |
| @VKIgnore | 字段 | 排除字段,不参与任何 SQL 操作 |
CRUD 操作
新增
User user = new User();
user.setName("Alice");
user.setEmail("alice@example.com");
Vostok.Data.insert(user); // 自增主键自动写回 user.id
// 批量新增(返回总成功行数)
List<User> users = List.of(user1, user2, user3);
int count = Vostok.Data.batchInsert(users);
// 批量新增(获取每条明细)
VKBatchDetailResult detail = Vostok.Data.batchInsertDetail(users);
for (VKBatchItemResult item : detail.getItems()) {
if (item.isSuccess()) {
System.out.println("index=" + item.getIndex() + " key=" + item.getKey());
} else {
System.out.println("失败: " + item.getError());
}
}
查询
// 按主键
User user = Vostok.Data.findById(User.class, 1L);
// 查全部
List<User> all = Vostok.Data.findAll(User.class);
// 条件查询(使用 VKQuery + VKCondition)
VKQuery q = VKQuery.create()
.where(VKCondition.of("email", VKOperator.EQ, "alice@example.com"))
.limit(10).offset(0);
List<User> list = Vostok.Data.query(User.class, q);
// 查询指定列(其余字段为默认值)
List<User> partial = Vostok.Data.queryColumns(
User.class, q, "id", "name"
);
更新 / 删除
user.setName("Bob");
Vostok.Data.update(user); // 按主键更新,返回影响行数
// 批量更新(返回总行数)
int rows = Vostok.Data.batchUpdate(users);
// 按主键删除
Vostok.Data.delete(User.class, 1L);
// 批量删除
Vostok.Data.batchDelete(User.class, List.of(1L, 2L, 3L));
// 批量操作明细(适合 CONTINUE 失败策略场景)
VKBatchDetailResult detail = Vostok.Data.batchDeleteDetail(User.class, ids);
System.out.println("成功: " + detail.totalSuccess() + " 失败: " + detail.totalFail());
任意 SQL 执行(DataResult 游标)
当 Query Builder 无法覆盖复杂场景时,可以直接执行原生 SQL。executeQuery 返回 DataResult,使用方式接近 JDBC ResultSet。
import yueyang.vostok.data.DataResult;
// 查询:next() + getXxx()
try (DataResult rs = Vostok.Data.executeQuery(
"SELECT id, user_name, age FROM t_user WHERE age > ?", 18)) {
while (rs.next()) {
long id = rs.getLong("id");
String name = rs.getString("user_name");
int age = rs.getInt("age");
if (rs.wasNull()) {
// age 列是 SQL NULL 时,getInt 返回 0,需要配合 wasNull() 判断
}
}
}
// 更新:返回影响行数
int n = Vostok.Data.executeUpdate(
"UPDATE t_user SET age = ? WHERE id = ?", 20, 1L
);
DataResult 语义
- 列索引为 1-based(与 JDBC ResultSet 一致)。
next() 到达末尾返回 false,并自动关闭资源。
- 建议始终使用
try-with-resources 显式关闭。
getInt/getLong/getDouble/getBoolean 读取到 SQL NULL 时会返回零值,需用 wasNull() 判断。
安全提示
executeQuery / executeUpdate 不经过 Raw SQL 白名单,请务必使用参数占位符(
?)绑定变量,避免字符串拼接导致注入风险。
乐观锁(@VKVersion)
在字段上标注 @VKVersion 即可启用乐观锁机制,无需额外配置。
@VKEntity(table = "orders")
public class Order {
@VKId
private Long id;
private String title;
@VKVersion
@VKColumn(name = "version")
private Long version; // Long 或 Integer 均可
}
// INSERT:version 未设置时自动初始化为 0
Order order = new Order();
order.setTitle("New Order");
Vostok.Data.insert(order);
// → INSERT INTO orders (title, version) VALUES (?, 0)
// UPDATE:自动追加 version = version + 1,同时校验旧版本
order.setTitle("Updated");
Vostok.Data.update(order);
// → UPDATE orders SET title = ?, version = version + 1 WHERE id = ? AND version = ?
// 成功后 order.getVersion() 自动 +1,与数据库保持同步
// 并发冲突:另一事务已先行更新时,抛出 VKOptimisticLockException
Order copy1 = Vostok.Data.findById(Order.class, 1L);
Order copy2 = Vostok.Data.findById(Order.class, 1L);
Vostok.Data.update(copy1); // 成功,版本 0→1
try {
Vostok.Data.update(copy2); // 失败:copy2 版本仍为 0,与数据库 1 不匹配
} catch (VKOptimisticLockException e) {
// 重新读取最新数据后重试
copy2 = Vostok.Data.findById(Order.class, 1L);
Vostok.Data.update(copy2);
}
说明
- 版本字段类型支持
Long、long、Integer、int,每个实体类最多一个 @VKVersion。
- 批量更新(
batchUpdate)同样在 WHERE 中带版本条件,但不抛异常——通过 VKBatchItemResult.getCount()==0 判断冲突。
update() 成功后,框架自动将实体版本字段 +1,无需手动同步。
逻辑删除(@VKLogicDelete)
标注 @VKLogicDelete 后,框架将所有物理删除转换为字段标记更新,所有查询自动过滤已删除记录,对业务代码完全透明。
@VKEntity(table = "articles")
public class Article {
@VKId
private Long id;
private String title;
@VKLogicDelete(deletedValue = "1", normalValue = "0")
@VKColumn(name = "is_deleted")
private Integer isDeleted;
}
// INSERT:is_deleted 未设置时自动初始化为 normalValue(0)
Article a = new Article();
a.setTitle("Hello");
Vostok.Data.insert(a);
// → INSERT INTO articles (title, is_deleted) VALUES (?, 0)
// DELETE:转为软删 UPDATE,物理记录保留
Vostok.Data.delete(Article.class, a.getId());
// → UPDATE articles SET is_deleted = 1 WHERE id = ?
// findById:自动过滤已删除记录,软删后返回 null
Article found = Vostok.Data.findById(Article.class, a.getId());
// found == null(记录被软删)
// → SELECT ... FROM articles WHERE id = ? AND is_deleted = 0
// findAll / query / count:自动注入 is_deleted = 0 过滤
List<Article> list = Vostok.Data.findAll(Article.class);
// → SELECT ... FROM articles WHERE is_deleted = 0
// 恢复逻辑删除记录:直接 update isDeleted = 0
Article restored = new Article();
restored.setId(a.getId());
restored.setTitle("Hello");
restored.setIsDeleted(0); // 恢复为 normalValue
Vostok.Data.update(restored);
说明
- 字段类型支持
String、Integer/int、Long/long、Boolean/boolean,注解的 deletedValue/normalValue 会按字段类型自动转换。
- 每个实体类最多一个
@VKLogicDelete 字段。
- 逻辑删除过滤对
query()、count()、aggregate() 等动态查询同样生效,调用方无需手动添加条件。
- 如需查询所有记录(含已删除),请使用原生 SQL 或注册 Raw SQL 白名单后执行。
列约束(nullable / length / unique)
通过 @VKColumn 的 nullable、length、unique 属性可以声明列级约束。这些属性仅在自动建表(autoCreateTable=true)时生效,不影响运行时的 INSERT / UPDATE 行为。
@VKEntity(table = "users")
public class User {
@VKId(auto = true)
private Long id;
// nullable=false → NOT NULL;length=100 → VARCHAR(100);unique=true → UNIQUE
@VKColumn(name = "email", nullable = false, length = 100, unique = true)
private String email;
// nullable=false, length=50 → VARCHAR(50) NOT NULL
@VKColumn(name = "username", nullable = false, length = 50)
private String username;
// 默认:nullable=true, length=255 → VARCHAR(255)
@VKColumn(name = "bio")
private String bio;
// Integer 包装类型 + nullable=false → INT NOT NULL
@VKColumn(name = "score", nullable = false)
private Integer score;
}
// 以上实体的自动建表 SQL(MySQL 方言):
// CREATE TABLE users (
// id BIGINT AUTO_INCREMENT NOT NULL,
// email VARCHAR(100) NOT NULL UNIQUE,
// username VARCHAR(50) NOT NULL,
// bio VARCHAR(255),
// score INT NOT NULL,
// PRIMARY KEY (id)
// )
| 属性 | 类型 | 默认值 | 说明 |
| nullable | boolean | true | 列是否允许为空;false 时自动建表生成 NOT NULL。对基本类型(int、long 等)无论此属性如何,始终生成 NOT NULL。 |
| length | int | 255 | 字符串(String)及枚举类型对应的 VARCHAR 长度。非字符串类型(INT、BIGINT 等)忽略此属性。加密字段固定使用 VARCHAR(1024),忽略 length。 |
| unique | boolean | false | 列是否添加唯一约束;true 时自动建表在列定义后追加 UNIQUE。主键列本身通过 PRIMARY KEY 保证唯一,无需设置此属性。 |
说明
- 这三个属性仅影响 DDL 自动建表,框架在运行时不会校验或拦截违反约束的值——约束由数据库本身强制执行。
- 对于已存在的表,修改这些属性不会触发 ALTER TABLE;需手动执行 DDL 或先删除表再让框架重建。
length 对 BLOB、DATE、TIMESTAMP、DECIMAL 等固定格式类型无效。
查询构建器 VKQuery
所有条件通过 VKCondition 构建,再组合到 VKQuery 中。
VKQuery q = VKQuery.create()
// AND 条件:status = 'active'
.where(VKCondition.of("status", VKOperator.EQ, "active"))
// AND 条件:age >= 18
.where(VKCondition.of("age", VKOperator.GE, 18))
// OR 条件组:role = 'admin' OR role = 'superuser'
.or(
VKCondition.of("role", VKOperator.EQ, "admin"),
VKCondition.of("role", VKOperator.EQ, "superuser")
)
.orderBy(VKOrder.desc("created_at"))
.orderBy(VKOrder.asc("name"))
.limit(20).offset(40)
.groupBy("department")
// HAVING 条件:count(*) > 5
.having(VKCondition.raw("count(*)", VKOperator.GT, 5));
List<User> result = Vostok.Data.query(User.class, q);
VKCondition 工厂方法
| 方法 | 说明 |
VKCondition.of(field, op, values...) | 字段条件,支持所有 VKOperator |
VKCondition.raw(expr, op, values...) | 原始 SQL 表达式条件,如 raw("count(*)", GT, 5) |
VKCondition.inSubquery(field, subquery, params...) | 字段 IN (子查询) |
VKCondition.notInSubquery(field, subquery, params...) | 字段 NOT IN (子查询) |
VKCondition.exists(subquery, params...) | EXISTS (子查询) |
VKCondition.notExists(subquery, params...) | NOT EXISTS (子查询) |
VKOperator 枚举
| 枚举值 | SQL 语义 |
EQ | = ? |
NE | != ? |
GT | > ? |
GE | >= ? |
LT | < ? |
LE | <= ? |
LIKE | LIKE ? |
IN | IN (...) |
NOT_IN | NOT IN (...) |
BETWEEN | BETWEEN ? AND ?(传两个值) |
IS_NULL | IS NULL |
IS_NOT_NULL | IS NOT NULL |
EXISTS | EXISTS (...) |
NOT_EXISTS | NOT EXISTS (...) |
VKOrder
VKOrder.asc("name") // ORDER BY name ASC
VKOrder.desc("created_at") // ORDER BY created_at DESC
复合条件组 VKConditionGroup
// 手动构造 AND 组或 OR 组
VKConditionGroup andGroup = VKConditionGroup.and(
VKCondition.of("status", VKOperator.EQ, "active"),
VKCondition.of("age", VKOperator.GE, 18)
);
VKConditionGroup orGroup = VKConditionGroup.or(
VKCondition.of("role", VKOperator.EQ, "admin"),
VKCondition.of("role", VKOperator.EQ, "moderator")
);
VKQuery q = VKQuery.create()
.whereGroup(andGroup)
.whereGroup(orGroup);
子查询示例
// 先注册子查询 SQL 白名单
Vostok.Data.registerSubquery("SELECT id FROM orders WHERE amount > ?");
VKQuery q = VKQuery.create()
.where(VKCondition.inSubquery(
"id",
"SELECT id FROM orders WHERE amount > ?",
1000
));
List<User> users = Vostok.Data.query(User.class, q);
聚合查询
VKQuery q = VKQuery.create()
.where(VKCondition.of("status", VKOperator.EQ, "active"));
// COUNT 统计
long total = Vostok.Data.count(User.class, q);
// 多维聚合,返回 List<Object[]>,每个 Object[] 对应一行结果
// 列顺序与 VKAggregate 传入顺序一致
List<Object[]> rows = Vostok.Data.aggregate(
User.class, q,
VKAggregate.count("id", "total"),
VKAggregate.avg("age", "avg_age"),
VKAggregate.max("score", "max_score")
);
for (Object[] row : rows) {
System.out.println("total=" + row[0] + " avg_age=" + row[1] + " max_score=" + row[2]);
}
VKAggregate 工厂方法
| 方法 | SQL |
VKAggregate.count(field, alias) | COUNT(field) AS alias |
VKAggregate.countAll(alias) | COUNT(*) AS alias |
VKAggregate.sum(field, alias) | SUM(field) AS alias |
VKAggregate.avg(field, alias) | AVG(field) AS alias |
VKAggregate.min(field, alias) | MIN(field) AS alias |
VKAggregate.max(field, alias) | MAX(field) AS alias |
事务
Lambda 事务(推荐)
// 无返回值事务(默认传播:REQUIRED,隔离:DEFAULT)
Vostok.Data.tx(() -> {
Vostok.Data.insert(order);
Vostok.Data.update(inventory);
});
// 有返回值事务
Order saved = Vostok.Data.tx(() -> {
Vostok.Data.insert(order);
return Vostok.Data.findById(Order.class, order.getId());
});
// 指定传播行为与隔离级别
Vostok.Data.tx(
() -> { Vostok.Data.insert(order); },
VKTxPropagation.REQUIRES_NEW,
VKTxIsolation.READ_COMMITTED
);
// 只读事务
List<User> users = Vostok.Data.tx(
() -> Vostok.Data.findAll(User.class),
VKTxPropagation.REQUIRED,
VKTxIsolation.REPEATABLE_READ,
true // readOnly
);
手动事务管理
Vostok.Data.beginTx();
try {
Vostok.Data.insert(order);
Vostok.Data.update(inventory);
Vostok.Data.commitTx();
} catch (Exception e) {
Vostok.Data.rollbackTx();
throw e;
}
// 指定传播与隔离级别
Vostok.Data.beginTx(VKTxPropagation.REQUIRED, VKTxIsolation.READ_COMMITTED);
// 只读
Vostok.Data.beginTx(VKTxPropagation.REQUIRED, VKTxIsolation.DEFAULT, true);
VKTxPropagation 枚举
| 枚举值 | 说明 |
REQUIRED | 有事务则加入,无则新建(默认) |
REQUIRES_NEW | 总是新建事务(挂起当前事务) |
SUPPORTS | 有事务则加入,无则非事务执行 |
NOT_SUPPORTED | 总是非事务执行 |
MANDATORY | 必须在已有事务中执行,否则抛出异常 |
NEVER | 不能在事务中执行,否则抛出异常 |
VKTxIsolation 枚举
| 枚举值 | 说明 |
DEFAULT | 使用数据库默认隔离级别 |
READ_UNCOMMITTED | 读未提交 |
READ_COMMITTED | 读已提交 |
REPEATABLE_READ | 可重复读 |
SERIALIZABLE | 串行化 |
多数据源
// 注册额外数据源
Vostok.Data.registerDataSource("analytics", new VKDataConfig()
.url("jdbc:postgresql://analytics-db:5432/stats")
.username("reader")
.password("secret"));
// 切换数据源执行(无返回值)
Vostok.Data.withDataSource("analytics", () -> {
Vostok.Data.insert(report);
});
// 切换数据源执行(有返回值)
List<Report> reports = Vostok.Data.withDataSource("analytics", () ->
Vostok.Data.findAll(Report.class)
);
异步线程上下文传播
// 方式一:先捕获上下文,再传入异步线程
VostokContext ctx = Vostok.Data.captureContext();
CompletableFuture.runAsync(() -> ctx.run(() -> {
Vostok.Data.findAll(User.class);
}));
// 方式二:使用 wrap() 自动捕获当前上下文,直接包装 Runnable / Supplier
Runnable task = Vostok.Data.wrap(() -> {
Vostok.Data.findAll(User.class);
});
CompletableFuture.runAsync(task);
Supplier<List<User>> supplier = Vostok.Data.wrap(() ->
Vostok.Data.findAll(User.class)
);
CompletableFuture.supplyAsync(supplier);
// 方式三:使用显式上下文包装
VostokContext ctx2 = Vostok.Data.captureContext();
Runnable task2 = Vostok.Data.wrap(ctx2, () -> {
Vostok.Data.findAll(User.class);
});
executor.submit(task2);
SQL 拦截器
// VKInterceptor 接口:beforeExecute 和 afterExecute 均为默认方法,按需覆写
Vostok.Data.registerInterceptor(new VKInterceptor() {
@Override
public void beforeExecute(String sql, Object[] params) {
System.out.println("执行 SQL: " + sql);
}
@Override
public void afterExecute(String sql, Object[] params,
long costMs, boolean success, Throwable error) {
if (costMs > 200) {
Vostok.Log.warn("慢 SQL: {} ms → {}", costMs, sql);
}
if (!success && error != null) {
Vostok.Log.error("SQL 失败: {}", error.getMessage());
}
}
});
// 清除所有拦截器
Vostok.Data.clearInterceptors();
SQL 白名单(安全防护)
安全说明
子查询和原始 SQL 表达式(
VKCondition.raw / inSubquery / exists ...)需要先注册到白名单,否则框架会拒绝执行,防止 SQL 注入风险。
executeQuery / executeUpdate 不走白名单校验。
// 注册允许执行的原始 SQL
Vostok.Data.registerRawSql(
"SELECT count(*) FROM audit_log WHERE created_at > ?"
);
// 注册子查询 SQL
Vostok.Data.registerSubquery(
"SELECT id FROM orders WHERE status = ?"
);
// 针对指定数据源注册
Vostok.Data.registerRawSql("analytics", new String[]{
"SELECT user_id FROM sessions WHERE active = ?"
});
Vostok.Data.registerSubquery("analytics", new String[]{
"SELECT id FROM events WHERE type = ?"
});
字段透明加密
Data 模块通过 @VKColumn(encrypted=true) 注解对数据库列实现透明加密:写入时自动加密,读取时自动解密,业务代码操作实体字段如同操作明文。底层使用 Security 模块的 vkf3 格式(AES-256-GCM + DEK/KEK 双层密钥),解密为自描述模式,密文头携带密钥标识,无需调用方额外传参。
字段级密钥隔离
每个加密字段使用独立的 columnKeyId,默认规则为
表名-列名(如
users-phone、
users-id_card)。每个字段持有独立的 DEK,一个字段的密钥轮换或泄露不影响其他字段。如需覆盖,通过
@VKColumn(keyId="custom-key") 显式指定。
前置条件
字段加密依赖 Security 模块 KeyStore。需先调用
Vostok.Security.initKeyStore() 初始化密钥存储,再启用 Data 模块字段加密。
// 1. 初始化 KeyStore(字段加密所需)
Vostok.Security.initKeyStore(new VKKeyStoreConfig()
.baseDir("./keys")
.masterKey("my-master-secret")
);
// 2. 开启加密功能(配置阶段)
Vostok.Data.init(
new VKDataConfig()
.url("jdbc:mysql://...")
.fieldEncryptionEnabled(true),
"com.example.entity"
);
// 3. 实体字段标记加密(自动加密存储、解密读取)
@VKEntity(table = "users")
public class User {
@VKId
private Long id;
// columnKeyId 自动推导为 "users-phone"(表名-列名)
@VKColumn(name = "phone", encrypted = true)
private String phone;
// columnKeyId 自动推导为 "users-id_card"
@VKColumn(name = "id_card", encrypted = true)
private String idCard;
// 显式指定 columnKeyId(覆盖自动推导)
@VKColumn(name = "secret", encrypted = true, keyId = "custom-key")
private String secret;
}
加密字段类型必须为 String,存储在数据库中的值为 vkf3 格式 Base64 字符串。解密采用自描述模式,密文头携带密钥哈希和 DEK 版本号,支持跨 DEK 版本解密(DEK 轮换后旧密文无需重加密)。
columnKeyId 推导规则
| 情况 | columnKeyId | 示例 |
未指定 keyId | 表名-列名 | users-phone、orders-address |
显式 @VKColumn(keyId="k") | k(原样使用) | custom-key |
存量数据迁移(加密 / 解密)
对已有明文数据执行批量加密,或将 vkf3 密文批量解密还原为明文。已是 vkf3 密文的行在加密时自动跳过(幂等),可分批、断点续跑。
迁移时的 columnKeyId
encryptKeyId 必须与字段在运行时使用的 columnKeyId 完全一致,否则加密结果无法被 Data 模块自动解密。
未指定
@VKColumn(keyId) 时,运行时 columnKeyId 为
表名-列名(如
users-phone),迁移时
encryptKeyId 应填写相同的值。
// 预览迁移计划(估算行数,不执行任何写入)
VKCryptoMigratePlan plan = Vostok.Data.previewEncrypt(
new VKCryptoMigrateOptions()
.table("users")
.idColumn("id")
.targetColumn("phone")
.encryptKeyId("users-phone") // 与字段运行时 columnKeyId 一致(表名-列名)
.batchSize(500)
);
System.out.println("预计行数: " + plan.getEstimatedRows());
// 执行批量加密迁移(明文 → vkf3 密文)
VKCryptoMigrateResult result = Vostok.Data.encryptColumn(
new VKCryptoMigrateOptions()
.table("users")
.idColumn("id")
.targetColumn("phone")
.encryptKeyId("users-phone") // columnKeyId = 表名-列名
.batchSize(500)
.skipOnError(true)
.useTransactionPerBatch(true)
);
System.out.printf("扫描=%d 成功=%d 失败=%d 耗时=%dms%n",
result.getScannedRows(), result.getUpdatedRows(),
result.getFailedRows(), result.getCostMs());
// 执行批量解密迁移(vkf3 密文 → 明文)
VKCryptoMigrateResult r2 = Vostok.Data.decryptColumn(
new VKCryptoMigrateOptions()
.table("users")
.idColumn("id")
.targetColumn("phone")
.whereSql("status = ?")
.whereParams("active")
.allowPlaintextRead(true) // 非 vkf3 行跳过,不报错
);
连接池监控
// 获取所有数据源的连接池指标
List<VKPoolMetrics> metricsList = Vostok.Data.poolMetrics();
for (VKPoolMetrics m : metricsList) {
System.out.printf("[%s] total=%d active=%d idle=%d%n",
m.getName(), m.getTotal(), m.getActive(), m.getIdle());
}
// 获取可读诊断报告
String report = Vostok.Data.report();
System.out.println(report);
元数据刷新
// 刷新所有已注册实体的元数据(不重新扫描包)
Vostok.Data.refreshMeta();
// 重新扫描指定包并刷新元数据
Vostok.Data.refreshMeta("com.example.entity", "com.example.model");
配置参数
| 参数 | 类型 | 默认值 | 说明 |
| 数据库连接 |
| url | String | — | JDBC 连接 URL(必填) |
| username | String | — | 数据库用户名 |
| password | String | — | 数据库密码 |
| driver | String | 自动推断 | JDBC 驱动类名 |
| dialect | VKDialectType | 自动推断 | SQL 方言:MYSQL / POSTGRESQL / ORACLE / DB2 / SQLSERVER |
| 连接池 |
| minIdle | int | 1 | 最小空闲连接数 |
| maxActive | int | 10 | 最大活跃连接数 |
| maxWaitMs | long | 30000 | 获取连接最大等待时间(ms) |
| testOnBorrow | boolean | false | 借出时校验连接是否存活 |
| testOnReturn | boolean | false | 归还时校验连接是否存活 |
| validationQuery | String | 自动 | 连接校验 SQL(优先于 isValid) |
| validationTimeoutSec | int | 2 | 连接校验超时(秒) |
| idleValidationIntervalMs | long | 0 | 空闲连接定期校验间隔(ms,<=0 不启用) |
| preheatEnabled | boolean | true | 启动时预热连接池至 minIdle |
| idleTimeoutMs | long | 0 | 空闲连接超时回收(ms,<=0 不回收) |
| leakDetectMs | long | 0 | 连接泄漏检测阈值(ms,0=关闭) |
| statementCacheSize | int | 50 | 每连接预编译 SQL 缓存大小 |
| sqlTemplateCacheSize | int | 200 | 每数据源 SQL 模板缓存大小 |
| externalDataSource | DataSource | — | 注入外部连接池(HikariCP 等) |
| closeExternalDataSource | boolean | false | 关闭模块时是否同时关闭外部连接池 |
| 重试 |
| retryEnabled | boolean | false | 操作失败后是否自动重试 |
| maxRetries | int | 2 | 最大重试次数 |
| retryBackoffBaseMs | long | 50 | 指数退避基数(ms) |
| retryBackoffMaxMs | long | 2000 | 指数退避上限(ms) |
| retrySqlStatePrefixes | String[] | ["08","40","57"] | 可重试 SQLState 前缀白名单 |
| 批操作 |
| batchSize | int | 500 | 批量操作每批大小 |
| batchFailStrategy | VKBatchFailStrategy | FAIL_FAST | 批操作失败策略:FAIL_FAST(首失败抛异常)/ CONTINUE(跳过继续) |
| SQL 日志与指标 |
| logSql | boolean | false | 打印执行的 SQL |
| logParams | boolean | false | 打印 SQL 参数(需配合 logSql) |
| slowSqlMs | long | 0 | 慢 SQL 阈值(ms,<=0 不记录) |
| sqlMetricsEnabled | boolean | true | 是否启用 SQL 耗时分布统计 |
| slowSqlTopN | int | 0 | 慢 SQL TopN 数量(0 不保存) |
| 事务 |
| savepointEnabled | boolean | true | 是否启用 Savepoint 支持 |
| txTimeoutMs | long | 0 | 事务超时(ms,0 不限制) |
| queryTimeoutMs | long | 0 | 非事务查询超时(ms,0 不限制) |
| DDL |
| autoCreateTable | boolean | false | 启动时自动建表 |
| validateDdl | boolean | false | 启动时校验实体与表结构是否一致 |
| ddlSchema | String | — | DDL 校验使用的 schema(可选) |
| 字段加密(vkf3) |
| fieldEncryptionEnabled | boolean | false | 是否开启字段透明加密(vkf3 格式) |
| allowPlaintextRead | boolean | false | 是否允许读取非 vkf3 明文(true = 迁移期跳过,false = 格式异常时抛异常) |
API 速查
初始化与生命周期
| 方法 | 说明 |
init(VKDataConfig, String... packages) | 初始化数据模块并扫描实体 |
registerDataSource(name, VKDataConfig) | 注册命名数据源 |
refreshMeta() | 刷新所有实体元数据 |
refreshMeta(String... packages) | 重扫指定包并刷新元数据 |
started() | 是否已初始化 |
close() | 关闭数据模块,释放连接池 |
CRUD
| 方法 | 返回值 | 说明 |
insert(entity) | int | 插入单条记录,自增主键写回实体 |
batchInsert(List) | int | 批量插入,返回总成功行数 |
batchInsertDetail(List) | VKBatchDetailResult | 批量插入,返回每条明细 |
update(entity) | int | 按主键更新,返回影响行数 |
batchUpdate(List) | int | 批量更新,返回总行数 |
batchUpdateDetail(List) | VKBatchDetailResult | 批量更新,返回每条明细 |
delete(Class, id) | int | 按主键删除,返回影响行数 |
batchDelete(Class, List ids) | int | 批量删除,返回总行数 |
batchDeleteDetail(Class, List ids) | VKBatchDetailResult | 批量删除,返回每条明细 |
findById(Class<T>, id) | T | 按主键查询,未找到返回 null |
findAll(Class<T>) | List<T> | 查询全部记录 |
query(Class<T>, VKQuery) | List<T> | 条件查询 |
queryColumns(Class<T>, VKQuery, fields...) | List<T> | 查询指定列,其余字段为默认值 |
executeQuery(sql, params...) | DataResult | 执行原生查询 SQL,返回游标结果 |
executeUpdate(sql, params...) | int | 执行原生更新 SQL,返回影响行数 |
count(Class, VKQuery) | long | 统计符合条件的记录数 |
aggregate(Class, VKQuery, VKAggregate...) | List<Object[]> | 多维聚合查询,列顺序与参数一致 |
事务
| 方法 | 说明 |
tx(Runnable) | Lambda 事务(无返回值,默认传播和隔离) |
tx(Runnable, propagation, isolation) | Lambda 事务(指定传播和隔离) |
tx(Runnable, propagation, isolation, readOnly) | Lambda 事务(含只读标志) |
tx(Supplier<T>) | Lambda 事务(有返回值,默认传播和隔离) |
tx(Supplier<T>, propagation, isolation) | Lambda 事务(有返回值,指定传播和隔离) |
tx(Supplier<T>, propagation, isolation, readOnly) | Lambda 事务(有返回值,含只读标志) |
beginTx() | 手动开启事务 |
beginTx(propagation, isolation) | 手动开启事务(指定传播和隔离) |
beginTx(propagation, isolation, readOnly) | 手动开启事务(含只读标志) |
commitTx() | 手动提交事务 |
rollbackTx() | 手动回滚事务 |
多数据源与异步
| 方法 | 说明 |
withDataSource(name, Runnable) | 切换数据源执行(无返回值) |
withDataSource(name, Supplier<T>) | 切换数据源执行(有返回值) |
captureContext() | 捕获当前线程上下文(数据源等) |
wrap(Runnable) | 包装 Runnable,自动绑定当前上下文 |
wrap(Supplier<T>) | 包装 Supplier,自动绑定当前上下文 |
wrap(VostokContext, Runnable) | 包装 Runnable,绑定指定上下文 |
wrap(VostokContext, Supplier<T>) | 包装 Supplier,绑定指定上下文 |
SQL 安全、拦截器与监控
| 方法 | 说明 |
registerRawSql(String... sqls) | 注册原始 SQL 白名单(默认数据源) |
registerRawSql(name, String[] sqls) | 注册原始 SQL 白名单(指定数据源) |
registerSubquery(String... sqls) | 注册子查询白名单(默认数据源) |
registerSubquery(name, String[] sqls) | 注册子查询白名单(指定数据源) |
registerInterceptor(VKInterceptor) | 注册 SQL 拦截器 |
clearInterceptors() | 清除所有已注册拦截器 |
poolMetrics() | 获取所有数据源连接池指标列表 |
report() | 获取可读诊断报告字符串 |
字段加密迁移
| 方法 | 说明 |
previewEncrypt(VKCryptoMigrateOptions) | 预览加密迁移计划(不执行) |
previewDecrypt(VKCryptoMigrateOptions) | 预览解密迁移计划(不执行) |
encryptColumn(VKCryptoMigrateOptions) | 执行存量数据批量加密迁移 |
decryptColumn(VKCryptoMigrateOptions) | 执行存量数据批量解密迁移 |
异常处理
try {
Vostok.Data.insert(user);
} catch (VKSqlException e) {
// SQL 执行失败:可获取失败 SQL、SQLState、数据库 vendor 错误码
System.err.println(e.getCode() + " " + e.getMessage());
System.err.println("SQL: " + e.getSql());
System.err.println("SQLState: " + e.getSqlState());
} catch (VKOptimisticLockException e) {
// 乐观锁冲突:@VKVersion 版本不匹配,UPDATE 影响行数为 0
// 建议:重新查询最新实体,合并变更后重试
} catch (VKPoolException e) {
// 连接池耗尽或连接获取超时
} catch (VKTxException e) {
// 事务提交/回滚/传播违规
} catch (VKArgumentException e) {
// 非法参数(如 null 实体)
} catch (VKConfigException e) {
// 数据源配置错误
} catch (VKMetaException e) {
// 实体元数据解析失败
}
异常类层次
| 异常类 | 错误码 | 触发场景 |
VKArgumentException | DK-400 | 非法参数,如传入 null |
VKStateException | DK-401 | 模块未初始化 |
VKConfigException | DK-402 | 配置错误 |
VKMetaException | DK-410 | 实体元数据解析失败 |
VKSqlException | DK-500~DK-504 | SQL 执行失败、超时、约束违反、语法错误、连接断开 |
VKScanException | DK-510 | 实体扫描失败 |
VKPoolException | DK-520 | 连接池耗尽或超时 |
VKTxException | DK-530 | 事务提交/回滚/传播违规 |
VKOptimisticLockException | DK-550 | 乐观锁冲突(版本号不匹配,UPDATE 影响行数为 0) |
完整错误码见 错误码参考。