本指南解释如何在 Tinystruct 应用程序中集成和使用数据库。
Tinystruct 为多种数据库系统提供内置支持:
- MySQL
- SQLite
- H2
- Redis
- Microsoft SQL Server
在属性文件中配置数据库连接:
# MySQL 配置
driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
database.user=root
database.password=password
database.connections.max=10
# H2 配置
# driver=org.h2.Driver
# database.url=jdbc:h2:~/test
# database.user=sa
# database.password=
# database.connections.max=10
# SQLite 配置
# driver=org.sqlite.JDBC
# database.url=jdbc:sqlite:mydb.sqlite
# database.user=
# database.password=
# database.connections.max=10Tinystruct 提供多种数据库访问方法:
- DatabaseOperator:一个方便的数据库操作工具类
- 直接仓库 API:使用 Repository 接口进行原始 SQL 查询和更新
- 对象映射:使用带有 XML 配置的映射 Java 对象,实现更面向对象的方法
DatabaseOperator 类提供了一种方便的方式来执行数据库操作,而无需直接管理 Repository 实例。它自动处理连接管理、语句准备和资源清理。
// 默认构造函数 - 从 ConnectionManager 获取连接
DatabaseOperator operator = new DatabaseOperator();
// 使用特定数据库
DatabaseOperator operator = new DatabaseOperator("myDatabase");
// 使用现有连接
Connection connection = getConnection();
DatabaseOperator operator = new DatabaseOperator(connection);// 无参数的简单查询
ResultSet results = operator.query("SELECT * FROM users");
// 带参数的查询(使用预处理语句)
PreparedStatement stmt = operator.preparedStatement("SELECT * FROM users WHERE id = ?", new Object[]{1});
ResultSet results = operator.executeQuery(stmt);
// 处理结果
while (results.next()) {
int id = results.getInt("id");
String name = results.getString("name");
// 处理行数据
}// 无参数的简单更新
int rowsAffected = operator.update("UPDATE users SET status = 'active'");
// 带参数的更新
PreparedStatement stmt = operator.preparedStatement(
"UPDATE users SET name = ? WHERE id = ?",
new Object[]{"张三", 1}
);
int rowsAffected = operator.executeUpdate(stmt);
// 执行可能是查询或更新的语句
boolean isResultSet = operator.execute("CALL some_procedure()");// 使用 try-with-resources 自动清理
try (DatabaseOperator operator = new DatabaseOperator()) {
ResultSet results = operator.query("SELECT * FROM users");
// 处理结果
} // 自动关闭 ResultSet、PreparedStatement,并将 Connection 返回到连接池DatabaseOperator 包含内置的 SQL 注入检测:
// 默认启用 SQL 注入检查
DatabaseOperator operator = new DatabaseOperator();
// 禁用 SQL 注入检查(例如,用于 CLI 工具)
operator.disableSafeCheck();Tinystruct 还使用仓库模式进行直接数据库操作。Repository 接口提供了执行查询和更新的方法。
// 创建 MySQL 仓库
Repository repository = Type.MySQL.createRepository();
// 创建 H2 仓库
Repository repository = Type.H2.createRepository();
// 创建 SQLite 仓库
Repository repository = Type.SQLite.createRepository();@Action("users")
public String getUser(Integer id, Request request, Response response) {
try {
// 创建 DatabaseOperator 实例
DatabaseOperator operator = new DatabaseOperator();
// 执行带参数的查询
ResultSet results = operator.query("SELECT id, name, email FROM users WHERE id = " + id);
// 设置内容类型为 JSON
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
if (!results.next()) {
// 创建错误响应
Builder builder = new Builder();
builder.put("error", "未找到用户");
return builder.toString();
}
// 创建成功响应
Builder builder = new Builder();
builder.put("id", results.getInt("id"));
builder.put("name", results.getString("name"));
builder.put("email", results.getString("email"));
return builder.toString();
} catch (Exception e) {
// 设置内容类型为 JSON
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
// 创建错误响应
Builder builder = new Builder();
builder.put("error", e.getMessage());
return builder.toString();
}
}@Action("users/create")
public String createUser(Request request, Response response) {
try {
String name = request.getParameter("name");
String email = request.getParameter("email");
if (name == null || email == null) {
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
Builder builder = new Builder();
builder.put("error", "名称和电子邮件是必需的");
return builder.toString();
}
// 创建 DatabaseOperator 实例
DatabaseOperator operator = new DatabaseOperator();
// 执行带参数的更新
PreparedStatement stmt = operator.preparedStatement(
"INSERT INTO users (name, email) VALUES (?, ?)",
new Object[]{name, email}
);
int result = operator.executeUpdate(stmt);
// 设置内容类型为 JSON
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
// 创建成功响应
Builder builder = new Builder();
builder.put("success", true);
builder.put("rowsAffected", result);
return builder.toString();
} catch (Exception e) {
// 设置内容类型为 JSON
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
// 创建错误响应
Builder builder = new Builder();
builder.put("error", e.getMessage());
return builder.toString();
}
}Tinystruct 通过 DatabaseOperator 类提供全面的事务支持。
try (DatabaseOperator operator = new DatabaseOperator()) {
// 开始事务
operator.beginTransaction();
try {
// 执行数据库操作
PreparedStatement stmt1 = operator.preparedStatement(
"INSERT INTO users (name) VALUES (?)",
new Object[]{"张三"}
);
operator.executeUpdate(stmt1);
PreparedStatement stmt2 = operator.preparedStatement(
"UPDATE settings SET value = ? WHERE name = ?",
new Object[]{"新值", "setting_name"}
);
operator.executeUpdate(stmt2);
// 如果所有操作都成功,则提交事务
operator.commitTransaction();
} catch (Exception e) {
// 如果任何操作失败,则回滚事务
operator.rollbackTransaction();
throw e;
}
}@Action("transfer")
public String transferFunds(Request request, Response response) {
int fromAccount = Integer.parseInt(request.getParameter("from"));
int toAccount = Integer.parseInt(request.getParameter("to"));
double amount = Double.parseDouble(request.getParameter("amount"));
try (DatabaseOperator operator = new DatabaseOperator()) {
// 开始事务
operator.beginTransaction();
try {
// 从源账户扣除
PreparedStatement stmt1 = operator.preparedStatement(
"UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
new Object[]{amount, fromAccount, amount}
);
int result1 = operator.executeUpdate(stmt1);
if (result1 == 0) {
operator.rollbackTransaction();
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
Builder builder = new Builder();
builder.put("error", "资金不足");
return builder.toString();
}
// 添加到目标账户
PreparedStatement stmt2 = operator.preparedStatement(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
new Object[]{amount, toAccount}
);
int result2 = operator.executeUpdate(stmt2);
if (result2 == 0) {
operator.rollbackTransaction();
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
Builder builder = new Builder();
builder.put("error", "未找到目标账户");
return builder.toString();
}
// 记录交易
PreparedStatement stmt3 = operator.preparedStatement(
"INSERT INTO transactions (from_account, to_account, amount, date) VALUES (?, ?, ?, NOW())",
new Object[]{fromAccount, toAccount, amount}
);
operator.executeUpdate(stmt3);
// 提交事务
operator.commitTransaction();
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
Builder builder = new Builder();
builder.put("success", true);
return builder.toString();
} catch (Exception e) {
// 出错时回滚
operator.rollbackTransaction();
throw e;
}
} catch (Exception e) {
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
Builder builder = new Builder();
builder.put("error", e.getMessage());
return builder.toString();
}
}保存点允许您在事务中创建点,您可以回滚到这些点,而无需回滚整个事务。
try (DatabaseOperator operator = new DatabaseOperator()) {
// 开始事务
operator.beginTransaction();
// 执行第一个操作
PreparedStatement stmt1 = operator.preparedStatement(
"INSERT INTO users (name) VALUES (?)",
new Object[]{"张三"}
);
operator.executeUpdate(stmt1);
// 在第一个操作后创建保存点
Savepoint savepoint = operator.createSavepoint("AFTER_INSERT");
try {
// 执行第二个操作
PreparedStatement stmt2 = operator.preparedStatement(
"UPDATE settings SET value = ? WHERE name = ?",
new Object[]{"新值", "setting_name"}
);
operator.executeUpdate(stmt2);
} catch (Exception e) {
// 如果第二个操作失败,回滚到保存点
operator.rollbackTransaction(savepoint);
// 尝试替代操作
PreparedStatement altStmt = operator.preparedStatement(
"INSERT INTO logs (message) VALUES (?)",
new Object[]{"操作失败"}
);
operator.executeUpdate(altStmt);
}
// 提交事务
operator.commitTransaction();
}DatabaseOperator 类提供以下与事务相关的方法:
beginTransaction():开始新事务commitTransaction():提交当前事务rollbackTransaction():回滚整个事务rollbackTransaction(Savepoint):回滚到特定保存点createSavepoint(String):创建命名保存点releaseSavepoint(Savepoint):释放保存点isInTransaction():检查事务是否活动
- 始终使用 try-with-resources 确保正确关闭
DatabaseOperator - 将事务操作包裹在 try-catch 块中
- 始终显式地提交或回滚事务
- 对于可能需要部分回滚的复杂操作,使用保存点
- 保持事务尽可能短,以避免长时间锁定资源
- 适当处理异常,确保在出错时回滚事务
注意:如果带有活动事务的 DatabaseOperator 在未显式提交或回滚事务的情况下关闭,事务将自动回滚以确保数据完整性。
Tinystruct 还支持使用通过 XML 配置文件映射到数据库表的 Java 对象进行面向对象的数据库访问。
创建代表数据库实体的 Java 类:
package custom.objects;
import org.tinystruct.data.component.AbstractData;
public class Book extends AbstractData {
private int id;
private String name;
private String author;
private String content;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}创建将 Java 类映射到数据库表的 XML 文件。将此文件放在资源目录中,路径与模型类的包结构相匹配:
<?xml version="1.0" encoding="UTF-8"?>
<mapping>
<class name="custom.objects.Book" table="books">
<property name="id" column="id" type="int" identifier="true"/>
<property name="name" column="name" type="string"/>
<property name="author" column="author" type="string"/>
<property name="content" column="content" type="string"/>
</class>
</mapping>@Action("books")
public String getBooks(Request request, Response response) {
try {
// 创建新的 Book 实例
Book book = new Book();
// 查找所有书籍
List<Book> books = book.findAll();
// 设置内容类型为 JSON
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
// 创建 JSON 响应
Builder builder = new Builder();
builder.put("books", books);
return builder.toString();
} catch (Exception e) {
// 处理错误
response.setStatus(ResponseStatus.INTERNAL_SERVER_ERROR);
Builder builder = new Builder();
builder.put("error", e.getMessage());
return builder.toString();
}
}
@Action("books")
public String getBook(Integer id, Request request, Response response) {
try {
// 创建新的 Book 实例
Book book = new Book();
// 设置要搜索的 ID
book.setId(id);
// 根据 ID 查找书籍
book.find();
// 设置内容类型为 JSON
response.headers().add(Header.CONTENT_TYPE.set("application/json"));
// 创建 JSON 响应
Builder builder = new Builder();
builder.put("book", book);
return builder.toString();
} catch (Exception e) {
// 处理错误
response.setStatus(ResponseStatus.INTERNAL_SERVER_ERROR);
Builder builder = new Builder();
builder.put("error", e.getMessage());
return builder.toString();
}
}// 创建新书籍
Book newBook = new Book();
newBook.setName("了不起的盖茨比");
newBook.setAuthor("F. 司科特·菲茨杰拉德");
newBook.setContent("在我年轻和更容易受伤的岁月里...");
newBook.append(); // 向数据库插入新记录
// 根据 ID 查找书籍
Book book = new Book();
book.setId(1);
book.findOneById(); // 根据 ID 查找
// 更新书籍
book.setName("更新的标题");
book.update();
// 删除书籍
book.delete(); // 删除记录
// 查找所有书籍
List<Book> allBooks = book.findAll();
// 条件查找书籍
List<Book> books = book.findWhere("author = ?", "F. 司科特·菲茨杰拉德");在 Tinystruct 框架中,不同的数据库操作有不同的方法:
append():专门用于向数据库插入新记录。update():专门用于更新数据库中的现有记录。save():此方法根据记录是否存在来决定是插入还是更新。它是一个便利方法,内部会根据需要调用append()或update()。
为了清晰和精确控制,建议使用 append() 进行插入操作,使用 update() 进行更新操作,而不是依赖 save()。
Tinystruct 包含一个内置的代码生成器,可以直接从数据库模式创建 POJO 类和 XML 映射文件。生成器支持 MySQL、MSSQL、SQLite 和 H2 数据库。
使用 generate CLI 命令调用生成器:
# 交互模式 — 生成器将提示输入表名和输出路径
bin/dispatcher generate
# 非交互模式 — 直接指定表名
bin/dispatcher generate --tables users
# 多个表(分号分隔)
bin/dispatcher generate --tables "users;orders;products"交互提示将询问:
- 表名 — 分号分隔(例如
users;orders) - 基础路径 — Java 文件的放置位置(默认:从项目的包结构自动检测)
生成器自动检测表列所需的 Java 类型,并将正确的 import 语句添加到生成的 POJO 中。您无需手动指定任何包。
以下类型映射将自动处理:
| SQL 列类型 | Java 类型 | 导入包 |
|---|---|---|
DATETIME、TIMESTAMP、DATETIME2 |
LocalDateTime |
java.time.LocalDateTime |
DATE |
Date |
java.util.Date |
TIMESTAMP(显式) |
Timestamp |
java.sql.Timestamp |
TIME |
Time |
java.sql.Time |
VARCHAR、CHAR、TEXT |
String |
(内置) |
INT、SMALLINT、TINYINT |
int |
(内置) |
BIGINT |
long |
(内置) |
FLOAT |
float |
(内置) |
DOUBLE |
double |
(内置) |
BLOB、BINARY、VARBINARY |
byte[] |
(内置) |
对于每个表,生成器生成两个文件:
- Java POJO — 例如
src/main/java/com/example/objects/User.java - XML 映射 — 例如
src/main/resources/com/example/objects/User.map.xml
对于包含列 id INT AUTO_INCREMENT、username VARCHAR(50)、email VARCHAR(100)、created_at DATETIME 的 users 表:
package com.example.objects;
import java.io.Serializable;
import java.time.LocalDateTime;
import org.tinystruct.data.component.AbstractData;
import org.tinystruct.data.component.Row;
public class User extends AbstractData implements Serializable {
private static final long serialVersionUID = ...L;
private String username;
private String email;
private LocalDateTime createdAt;
public Integer getId() {
return Integer.parseInt(this.Id.toString());
}
public void setUsername(String username) {
this.username = this.setFieldAsString("username", username);
}
public String getUsername() {
return this.username;
}
public void setEmail(String email) {
this.email = this.setFieldAsString("email", email);
}
public String getEmail() {
return this.email;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = this.setFieldAsLocalDateTime("createdAt", createdAt);
}
public LocalDateTime getCreatedAt() {
return this.createdAt;
}
@Override
public void setData(Row row) {
if(row.getFieldInfo("id") != null)
this.setId(row.getFieldInfo("id").intValue());
if(row.getFieldInfo("username") != null)
this.setUsername(row.getFieldInfo("username").stringValue());
if(row.getFieldInfo("email") != null)
this.setEmail(row.getFieldInfo("email").stringValue());
if(row.getFieldInfo("created_at") != null)
this.setCreatedAt(row.getFieldInfo("created_at").localDateTimeValue());
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("{");
buffer.append("\"Id\":" + this.getId());
buffer.append(",\"username\":\"" + this.getUsername() + "\"");
buffer.append(",\"email\":\"" + this.getEmail() + "\"");
buffer.append(",\"createdAt\":\"" + this.getCreatedAt() + "\"");
buffer.append("}");
return buffer.toString();
}
}<?xml version="1.0" encoding="UTF-8"?>
<mapping>
<class name="User" table="users">
<id name="Id" column="id" increment="true" generate="false" length="11" type="INT"/>
<property name="username" column="username" length="50" type="VARCHAR"/>
<property name="email" column="email" length="100" type="VARCHAR"/>
<property name="createdAt" column="created_at" length="0" type="DATETIME"/>
</class>
</mapping>生成的类继承自 AbstractData,因此可以直接与 tinystruct 的 ORM 集成:
// 创建
User user = new User();
user.setUsername("john");
user.setEmail("john@example.com");
user.setCreatedAt(LocalDateTime.now());
user.append();
// 读取
User found = new User();
found.setId(1);
found.findOneById();
// 更新
found.setEmail("newemail@example.com");
found.update();
// 删除
found.delete();-
连接管理:完成后始终关闭数据库连接。
-
参数化查询:使用参数化查询防止 SQL 注入。
-
事务:对需要原子性的操作使用事务。
-
错误处理:为数据库操作实现适当的错误处理。
-
连接池:为应用程序需求配置适当的连接池设置。
-
对象映射:在处理数据库实体时,使用对象映射方法可以获得更清晰、更易维护的代码。
-
XML 映射文件:将 XML 映射文件组织在与 Java 包结构相匹配的目录结构中。
- 了解高级特性
- 探索最佳实践
- 查看数据库 API 参考