本指南解释如何在 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()。
-
连接管理:完成后始终关闭数据库连接。
-
参数化查询:使用参数化查询防止 SQL 注入。
-
事务:对需要原子性的操作使用事务。
-
错误处理:为数据库操作实现适当的错误处理。
-
连接池:为应用程序需求配置适当的连接池设置。
-
对象映射:在处理数据库实体时,使用对象映射方法可以获得更清晰、更易维护的代码。
-
XML 映射文件:将 XML 映射文件组织在与 Java 包结构相匹配的目录结构中。
- 了解高级特性
- 探索最佳实践
- 查看数据库 API 参考