历史版本 v6.2.4。 本页内容为历史发布版本,不再接受更新。

查看本页在 v6.2.5 中的版本 →

Skip to content

数据储存

UltiTools 封装了一套数据储存 API,它支持 MySQL 数据库、SQLite 数据库(6.1.0起)与 JSON 文件储存。数据存储对于开发者来说是透明的,UltiTools将通过服主的配置判断使用哪种存储方式。

你需要的仅仅只是一个实体类。CRUD 操作将由 UltiTools 自动完成。

尽量不要嵌套对象

由于插件还处于开发状态,难免在处理复杂对象时出现问题,所以存储的对象尽量不要超过两层嵌套(尽量不要嵌套对象)。

创建实体类

BaseDataEntity

创建一个继承 BaseDataEntity<String> 的类,并使用 @Table@Column 注解来标记你的实体类。

java
package com.ultikits.docs.data;

import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.Column;
import com.ultikits.ultitools.annotations.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Table("user_data")
public class UserData extends BaseDataEntity<String> {
    @Column("player_name")
    private String playerName;

    @Column(value = "balance", type = "FLOAT")
    private double balance;
}

其中,@Table 注解用于标记该类对应的数据表(若使用 MySQL 数据库),@Column 注解用于标记该类的字段对应的数据表的列。

@Data@Builder@NoArgsConstructor@AllArgsConstructor@EqualsAndHashCode 则为 Lombok 注解,用于自动生成 gettersetterbuilderequalshashCode 方法。

从 AbstractDataEntity 迁移

从 v6.2.0 开始,DataOperatorQueryUltiToolsPlugin.getDataOperator() 要求实体继承 BaseDataEntity<String> 而非 AbstractDataEntity。如果你的实体仍然继承 AbstractDataEntity,请改为 BaseDataEntity<String>

BaseDataEntity<String> 提供了插入/更新/删除/加载事件的生命周期钩子:

方法说明
onCreate()在实体首次持久化之前调用
onUpdate()在实体更新之前调用
onDelete()在实体删除之前调用
onLoad()在从数据存储加载实体后调用
validate()实体有效返回 true
isNew()实体无 ID 时返回 true
copyWithoutId()创建不含 ID 的实体副本

AuditableDataEntity

对于需要跟踪创建和修改的实体,可以使用 AuditableDataEntity

java
package com.ultikits.docs.data;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.abstracts.data.AuditableDataEntity;
import com.ultikits.ultitools.abstracts.data.BaseDataEntity;
import com.ultikits.ultitools.annotations.*;
import com.ultikits.ultitools.interfaces.DataOperator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.bukkit.entity.Player;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Table("audit_log")
public class AuditEntry extends AuditableDataEntity<String> {
    @Column("action")
    private String action;
    @Column("details")
    private String details;
}

AuditableDataEntity<String> 继承了 BaseDataEntity<String>,自动管理以下字段:

字段类型说明
createdAtLocalDateTime实体创建时间(在 onCreate() 中自动设置)
updatedAtLocalDateTime上次修改时间(在 onUpdate() 中自动更新)
createdByUUID创建实体的用户 ID(从线程本地上下文获取)
updatedByUUID上次修改实体的用户 ID(从线程本地上下文获取)

所有这四个字段都已预配置 @Column 注解,子类中无需声明。

用户上下文管理

要跟踪执行操作的用户,需要在数据库操作前设置当前用户:

java
import com.ultikits.ultitools.abstracts.data.AuditableDataEntity;

UUID currentUserId = player.getUniqueId();
AuditableDataEntity.setCurrentUser(currentUserId);

try {
    DataOperator<AuditEntry> op = plugin.getDataOperator(AuditEntry.class);
    AuditEntry entry = AuditEntry.builder()
        .action("login")
        .details("玩家从 192.168.1.1 登录")
        .build();
    op.insert(entry);  // createdBy 和 updatedBy 自动设置
} finally {
    AuditableDataEntity.clearCurrentUser();
}

必须清除上下文

使用 try-finally 块确保调用 clearCurrentUser(),否则 ThreadLocal 上下文会持续存在于后续请求中,可能导致用户身份泄露。

工具方法

AuditableDataEntity 提供了便利的时间相关查询方法:

方法返回值说明
getAge()Durationnull实体自创建以来经过的时间
getTimeSinceUpdate()Durationnull实体自上次修改以来经过的时间
wasModified()boolean实体是否在创建后被修改过

使用示例:

java
AuditEntry entry = op.getById("some-id");
if (entry.wasModified()) {
    System.out.println("修改于 " + entry.getTimeSinceUpdate().getSeconds() + " 秒前");
}

空值安全

如果实体尚未持久化(缺少 createdAtupdatedAt),getAge()getTimeSinceUpdate() 会返回 null。在调用返回的 Duration 上的方法前,务必检查 null。

@Table 注解

@Table 注解有一个 value 属性,用于指定该类对应的数据表或文件夹的名称。

@Column 注解

@Column 注解有三个属性,value 属性用于指定该字段对应的数据表的列,type 属性用于指定该字段对应的数据表的列的类型。

type 属性的默认值为 VARCHAR(255)

可用的类型可参见 MySQL 数据类型

CRUD 操作

UltiTools 封装了一套语义化的 CRUD 操作 API,你只需要调用相应的方法,即可完成对数据的增删改查。

DataOperator

DataOperator 用于数据操作。

在继承了 UltiToolsPlugin 的主类中,有一个 getDataOperator 方法,用于获取数据操作器。

你需要获取插件主类的实例,然后调用 getDataOperator 方法。

java
package com.ultikits.docs.data;

import com.ultikits.ultitools.abstracts.UltiToolsPlugin;
import com.ultikits.ultitools.entities.WhereCondition;
import com.ultikits.ultitools.interfaces.DataOperator;

import java.util.List;

public class UserDataService {

    public void save(UltiToolsPlugin plugin, UserData data) {
        DataOperator<UserData> operator = plugin.getDataOperator(UserData.class);
        operator.insert(data);
    }

    public List<UserData> findByName(UltiToolsPlugin plugin, String name) {
        DataOperator<UserData> operator = plugin.getDataOperator(UserData.class);
        return operator.getAll(
                WhereCondition.builder().column("player_name").value(name).build()
        );
    }
}

请即取即用

DataOperator 不是线程安全的,请在需要的时候获取 DataOperator,不要试图保存 DataOperator 对象。

插入

java
SomeEntity entity = SomeEntity.builder()
    .name("test")
    .something(42.0)
    .build();
dataOperator.insert(entity);

查询

使用 WhereCondition

java
List<SomeEntity> list = dataOperator.getAll(
    WhereCondition.builder()
        .column("name")
        .value("test")
        .build()
);

或按 ID 获取单个实体:

java
SomeEntity entity = dataOperator.getById("some-id");

获取所有实体:

java
List<SomeEntity> all = dataOperator.getAll();

分页查询:

java
List<SomeEntity> page = dataOperator.page(1, 10); // 第 1 页,每页 10 条

查询 DSL

从 v6.2.0 开始,你可以使用流式查询 DSL 来编写更可读的查询:

java
SomeEntity entity = dataOperator.query()
    .where("name").eq("test")
    .first();

更新

更新单个字段:

java
dataOperator.update("name", "newName", entityId);

使用实体对象更新:

java
entity.setName("newName");
dataOperator.update(entity);

删除

按 ID 删除:

java
dataOperator.delById(entityId);

按条件删除:

java
dataOperator.del(
    WhereCondition.builder()
        .column("name")
        .value("test")
        .build()
);

WhereCondition

WhereCondition 用于指定查询条件。

java
WhereCondition.builder().column("somecol").value(someval).build();

其中,column 属性用于指定查询的列,value 属性用于指定查询的值。

事务

对于需要同时成功或同时失败的操作,请参阅事务指南。

java
dataOperator.transaction(() -> {
    dataOperator.insert(entity1);
    dataOperator.insert(entity2);
    // 全部插入或全部不插入
});

贡献者

The avatar of contributor named as Ling Bao Ling Bao
The avatar of contributor named as Claude Fable 5.1 Claude Fable 5.1

基于 MIT 许可发布