jooq中record
jOOQ是一个库,可帮助我们重新控制SQL。 它可以从我们的数据库生成代码,并允许我们使用其流畅的API来构建类型安全的数据库查询。
本教程前面的部分向我们介绍了如何配置示例应用程序的应用程序上下文以及如何从数据库中生成代码。
现在,我们准备向前迈出一步,学习如何使用jOOQ创建类型安全查询。 这篇博客文章描述了如何将CRUD操作添加到管理待办事项的简单应用程序中。
让我们开始吧。
补充阅读:
- 将jOOQ与Spring结合使用:配置是本教程的第一部分,它描述了您可以配置使用jOOQ的Spring应用程序的应用程序上下文。 您可以在不阅读本教程第一部分的情况下了解此博客文章,但是,如果您想在Spring支持的应用程序中真正使用jOOQ,建议您也阅读本教程的第一部分。
- 将jOOQ与Spring结合使用:代码生成是本教程的第二部分,它描述了如何对数据库进行反向工程并创建代表不同数据库表,记录等的jOOQ查询类。 因为这些类是类型安全SQL查询的构建块, 所以建议您在阅读本博客文章之前阅读本教程的第二部分 。
创建Todo类
让我们从创建一个包含单个待办事项条目信息的类开始。 此类具有以下字段:
- ID字段包含待办事项的ID。
- creationTime字段包含一个时间戳,该时间戳描述了todo条目第一次被持久保存的时间。
- 描述字段包含待办事项的描述。
- ModifyTime字段包含一个时间戳,该时间戳描述了待办事项条目的更新时间。
- 标题字段包含待办事项的标题。
这个相对简单的类的名称为Todo ,它遵循以下三个原则:
- 我们可以使用Joshua Bloch在Effective Java中描述的构建器模式来创建新的Todo对象。 如果您不熟悉此模式,则应阅读标题为项目2的文章:面对许多构造函数参数时,请考虑使用构建器 。
- 标题字段是必填字段,我们不能创建标题为空或为空的新Todo对象。 如果我们尝试创建标题无效的Todo对象,则会抛出IllegalStateException 。
- 此类是不可变的。 换句话说,其所有字段都声明为final 。
Todo类的源代码如下所示:
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.joda.time.LocalDateTime;
import java.sql.Timestamp;
public class Todo {
private final Long id;
private final LocalDateTime creationTime;
private final String description;
private final LocalDateTime modificationTime;
private final String title;
private Todo(Builder builder) {
this.id = builder.id;
LocalDateTime creationTime = null;
if (builder.creationTime != null) {
creationTime = new LocalDateTime(builder.creationTime);
}
this.creationTime = creationTime;
this.description = builder.description;
LocalDateTime modificationTime = null;
if (builder.modificationTime != null) {
modificationTime = new LocalDateTime(builder.modificationTime);
}
this.modificationTime = modificationTime;
this.title = builder.title;
}
public static Builder getBuilder(String title) {
return new Builder(title);
}
//Getters are omitted for the sake of clarity.
public static class Builder {
private Long id;
private Timestamp creationTime;
private String description;
private Timestamp modificationTime;
private String title;
public Builder(String title) {
this.title = title;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder creationTime(Timestamp creationTime) {
this.creationTime = creationTime;
return this;
}
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder modificationTime(Timestamp modificationTime) {
this.modificationTime = modificationTime;
return this;
}
public Todo build() {
Todo created = new Todo(this);
String title = created.getTitle();
if (title == null || title.length() == 0) {
throw new IllegalStateException("title cannot be null or empty");
}
return created;
}
}
}
让我们找出为什么我们需要获取当前日期和时间,更重要的是,什么是最好的方法。
获取当前日期和时间
因为每个待办事项的创建时间和修改时间都存储在数据库中,所以我们需要一种获取当前日期和时间的方法。 当然,我们可以简单地在存储库中创建此信息。 问题是,如果这样做,我们将无法编写自动测试来确保正确设置了创建时间和修改时间(我们无法为这些字段编写断言,因为它们的值取决于当前时间) 。
这就是为什么我们需要创建一个单独的组件来负责返回当前日期和时间的原因。 DateTimeService接口声明了以下两种方法:
- getCurrentDateTime()方法将当前日期和时间作为LocalDateTime对象返回。
- getCurrentTimestamp()方法将当前日期和时间作为Timestamp对象返回。
DateTimeService接口的源代码如下所示:
import org.joda.time.LocalDateTime;
import java.sql.Timestamp;
public interface DateTimeService {
public LocalDateTime getCurrentDateTime();
public Timestamp getCurrentTimestamp();
}
因为我们的应用程序对“实时”感兴趣,所以我们必须实现此接口并创建一个返回实际日期和时间的组件。 我们可以按照以下步骤进行操作:
- 创建一个实现DateTimeService接口的CurrentTimeDateTimeService类。
- 用@Profile注释为类添加注释,并将配置文件的名称设置为“ application”。 这意味着,当活动的Spring配置文件为“应用程序”时,可以将组件注册到Spring容器。
- 用@Component注释对类进行注释。 这样可以确保在类路径扫描期间找到该类。
- 实现在DateTimeService接口中声明的方法。 每个方法都必须返回当前日期和时间。
CurrentTimeDateTimeService的源代码如下所示:
import org.joda.time.LocalDateTime;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import java.sql.Timestamp;
@Profile("application")
@Component
public class CurrentTimeDateTimeService implements DateTimeService {
@Override
public LocalDateTime getCurrentDateTime() {
return LocalDateTime.now();
}
@Override
public Timestamp getCurrentTimestamp() {
return new Timestamp(System.currentTimeMillis());
}
}
让我们继续并开始实现示例应用程序的存储库层。
实施存储库层
首先,我们创建一个存储库接口,该接口为待办事项提供CRUD操作。 该接口声明了以下五种方法:
- Todo add(Todo todoEntry)方法将新的todo条目保存到数据库中,并返回保存的todo条目的信息。
- Todo delete(Long id)方法删除一个待办事项,并返回已删除的待办事项。
- List findAll()方法返回从数据库中找到的所有待办事项条目。
- Todo findById(Long id)返回单个todo条目的信息。
- Todo更新(Todo todoEntry)更新待办事项的信息并返回更新后的待办事项。
TodoRepository接口的源代码如下所示:
import java.util.List;
public interface TodoRepository {
public Todo add(Todo todoEntry);
public Todo delete(Long id);
public List<Todo> findAll();
public Todo findById(Long id);
public Todo update(Todo todoEntry);
}
接下来,我们必须实现TodoRepository接口。 当我们这样做时,我们必须遵循以下规则:
jOOQ创建的所有数据库查询都必须在事务内执行 。 这是因为我们的应用程序使用TransactionAwareDataSourceProxy类 ,并且如果我们在没有事务的情况下执行数据库查询,则jOOQ将为每个操作使用不同的连接。 这可能会导致竞赛条件错误。
通常,服务层充当事务边界,并且对jOOQ存储库的每次调用都应在事务内部进行。 但是,由于程序员也会犯错误,因此我们不能相信情况就是如此。 这就是为什么我们必须使用@Transactional批注来批注存储库类或其方法。
既然已经了解了这一点,就可以创建存储库类了。
创建存储库类
我们可以按照以下步骤创建存储库类的“骨架”:
- 创建一个JOOQTodoRepository类并实现TodoRepository接口。
- 用@Repository批注对类进行批注。 这样可以确保在类路径扫描期间找到该类。
- 将DateTimeService字段添加到创建的类。 我们记得, DateTimeService接口声明用于获取当前日期和时间的方法。
- 将DSLContext字段添加到创建的类。 该接口充当jOOQ API的入口点,我们可以使用它来构建SQL查询。
- 将公共构造函数添加到创建的类中,并使用@Autowired注释对构造函数进行注释。 这可以确保通过使用构造函数注入来注入存储库的依赖项。
- 将私有的Todo convertQueryResultToModelObject(TodosRecord queryResult)方法添加到存储库类。 此实用程序方法由我们的存储库类的公共方法使用。 通过执行以下步骤来实现此方法:
- 通过使用作为方法参数给出的TodosRecord对象的信息来创建新的Todo对象。
- 返回创建的对象。
JOOQTodoRepository类的相关部分如下所示:
import net.petrikainulainen.spring.jooq.todo.db.tables.records.TodosRecord;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class JOOQTodoRepository implements TodoRepository {
private final DateTimeService dateTimeService;
private final DSLContext jooq;
@Autowired
public JOOQTodoRepository(DateTimeService dateTimeService, DSLContext jooq) {
this.dateTimeService = dateTimeService;
this.jooq = jooq;
}
private Todo convertQueryResultToModelObject(TodosRecord queryResult) {
return Todo.getBuilder(queryResult.getTitle())
.creationTime(queryResult.getCreationTime())
.description(queryResult.getDescription())
.id(queryResult.getId())
.modificationTime(queryResult.getModificationTime())
.build();
}
}
让我们继续并实现为待办事项提供CRUD操作的方法。
添加新的待办事项
TodoRepository接口的公共Todo add(Todo todoEntry)方法用于向数据库添加新的todo条目。 我们可以通过执行以下步骤来实现此方法:
- 将一个私有TodosRecord createRecord(Todo todoEntry)方法添加到存储库类,并按照以下步骤实现此方法:
- 通过调用DateTimeService接口的getCurrentTimestamp()方法获取当前日期和时间。
- 创建一个新的TodosRecord对象,并使用作为方法参数给出的Todo对象的信息来设置其字段值。
- 返回创建的TodosRecord对象。
- 将add()方法添加到JOOQTodoRepository类中,并使用@Transactional注释对方法进行注释。 这样可以确保INSERT语句在读写事务中执行。
- 通过执行以下步骤来实现add()方法:
- 通过执行以下步骤将新的待办事项条目添加到数据库:
- 通过调用DSLContext接口的insertInto(Table table)方法来创建新的INSERT语句,并指定要向todos表中插入信息。
- 通过调用createRecord()方法创建一个新的TodosRecord对象。 将Todo对象作为方法参数传递。
- 通过调用InsertSetStep接口的set(Record record)方法来设置插入的信息。 将创建的TodosRecord对象作为方法参数传递。
- 通过调用InsertReturningStep接口的returning()方法,确保INSERT查询返回所有插入的字段。
- 通过调用InsertResultStep接口的fetchOne ()方法,获取包含所有插入字段的值的TodosRecord对象。
- 通过调用convertQueryResultToModelObject()方法,将INSERT语句返回的TodosRecord对象转换为Todo对象。
- 返回创建的Todo对象。
- 通过执行以下步骤将新的待办事项条目添加到数据库:
JOOQTodoRepository类的相关部分如下所示:
import net.petrikainulainen.spring.jooq.todo.db.tables.records.TodosRecord;
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Timestamp;
import static net.petrikainulainen.spring.jooq.todo.db.tables.Todos.TODOS;
@Repository
public class JOOQTodoRepository implements TodoRepository {
private final DateTimeService dateTimeService;
private final DSLContext jooq;
//The constructor is omitted for the sake of clarity
@Transactional
@Override
public Todo add(Todo todoEntry) {
TodosRecord persisted = jooq.insertInto(TODOS)
.set(createRecord(todoEntry))
.returning()
.fetchOne();
return convertQueryResultToModelObject(persisted);
}
private TodosRecord createRecord(Todo todoEntry) {
Timestamp currentTime = dateTimeService.getCurrentTimestamp();
TodosRecord record = new TodosRecord();
record.setCreationTime(currentTime);
record.setDescription(todoEntry.getDescription());
record.setModificationTime(currentTime);
record.setTitle(todoEntry.getTitle());
return record;
}
private Todo convertQueryResultToModelObject(TodosRecord queryResult) {
return Todo.getBuilder(queryResult.getTitle())
.creationTime(queryResult.getCreationTime())
.description(queryResult.getDescription())
.id(queryResult.getId())
.modificationTime(queryResult.getModificationTime())
.build();
}
}
4.3.3节。 jOOQ参考手册的INSERT语句提供了有关将数据插入数据库的其他信息。
让我们继续前进,找出如何找到存储在数据库中的所有条目。
查找所有待办事项
TodoRepository接口的公共List findAll()方法返回所有存储在数据库中的待办事项。 我们可以通过执行以下步骤来实现此方法:
- 将findAll()方法添加到存储库类,并使用@Transactional批注对该方法进行批注。 将其readOnly属性的值设置为true 。 这样可以确保SELECT语句在只读事务中执行。
- 通过执行以下步骤从数据库获取所有待办事项条目:
- 通过调用DSLContext接口的selectFrom(Table table)方法来创建新的SELECT语句,并指定要从todos表中选择信息。
- 通过调用ResultQuery接口的fetchInto(Class type)方法获取TodosRecord对象的列表。
- 迭代返回的TodosRecord对象列表,并通过调用convertQueryResultToModelObject()方法将每个TodosRecord对象转换为Todo对象。 每个ToDo对象添加到的Todo对象的列表。
- 返回包含找到的Todo对象的列表 。
JOOQTodoRepository类的相关部分如下所示:
import net.petrikainulainen.spring.jooq.todo.db.tables.records.TodosRecord;
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import static net.petrikainulainen.spring.jooq.todo.db.tables.Todos.TODOS;
@Repository
public class JOOQTodoRepository implements TodoRepository {
private final DSLContext jooq;
//The constructor is omitted for the sake of clarity
@Transactional(readOnly = true)
@Override
public List<Todo> findAll() {
List<Todo> todoEntries = new ArrayList<>();
List<TodosRecord> queryResults = jooq.selectFrom(TODOS).fetchInto(TodosRecord.class);
for (TodosRecord queryResult: queryResults) {
Todo todoEntry = convertQueryResultToModelObject(queryResult);
todoEntries.add(todoEntry);
}
return todoEntries;
}
private Todo convertQueryResultToModelObject(TodosRecord queryResult) {
return Todo.getBuilder(queryResult.getTitle())
.creationTime(queryResult.getCreationTime())
.description(queryResult.getDescription())
.id(queryResult.getId())
.modificationTime(queryResult.getModificationTime())
.build();
}
}
4.3.2节。 jOOQ参考手册的SELECT语句提供了有关从数据库中选择信息的更多信息。
接下来,我们将找到如何从数据库中获得一个待办事项。
查找单个待办事项
TodoRepository接口的公共Todo findById(Long id)方法返回单个todo条目的信息。 我们可以通过执行以下步骤来实现此方法:
- 在存储库类中添加findById()方法,并使用@Transactional注释对方法进行注释。 将其readOnly属性的值设置为true。 这样可以确保SELECT语句在只读事务中执行。
- 通过执行以下步骤,从数据库中获取单个待办事项的信息:
- 通过调用DSLContext接口的selectFrom(Table table)方法来创建新的SELECT语句,并指定要从todos表中选择信息。
- 通过调用SelectWhereStep接口的where(Collection condition)方法来指定SELECT语句的WHERE子句。 确保SELECT语句仅返回ID作为方法参数给出的todo条目。
- 通过调用ResultQuery接口的fetchOne ()方法来获取TodosRecord对象。
- 如果返回的TodosRecord对象为null,则表示未找到具有给定id的todo条目。 如果是这种情况,请抛出一个新的TodoNotFoundException 。
- 通过调用convertQueryResultToModelObject()方法,将SELECT语句返回的TodosRecord对象转换为Todo对象。
- 返回创建的Todo对象。
JOOQTodoRepository的相关部分如下所示:
import net.petrikainulainen.spring.jooq.todo.db.tables.records.TodosRecord;
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import static net.petrikainulainen.spring.jooq.todo.db.tables.Todos.TODOS;
@Repository
public class JOOQTodoRepository implements TodoRepository {
private final DSLContext jooq;
//The constructor is omitted for the sake of clarity.
@Transactional(readOnly = true)
@Override
public Todo findById(Long id) {
TodosRecord queryResult = jooq.selectFrom(TODOS)
.where(TODOS.ID.equal(id))
.fetchOne();
if (queryResult == null) {
throw new TodoNotFoundException("No todo entry found with id: " + id);
}
return convertQueryResultToModelObject(queryResult);
}
private Todo convertQueryResultToModelObject(TodosRecord queryResult) {
return Todo.getBuilder(queryResult.getTitle())
.creationTime(queryResult.getCreationTime())
.description(queryResult.getDescription())
.id(queryResult.getId())
.modificationTime(queryResult.getModificationTime())
.build();
}
}
4.3.2节。 jOOQ参考手册的SELECT语句提供了有关从数据库中选择信息的更多信息。
让我们找出如何从数据库中删除待办事项。
删除待办事项
TodoRepository接口的公共Todo delete(Long id)方法用于从数据库中删除一个todo条目。 我们可以通过执行以下步骤来实现此方法:
- 将delete()方法添加到存储库类中,并使用@Transactional批注对该方法进行批注。 这确保了DELETE语句在读写事务内执行。
- 通过执行以下步骤来实现此方法:
- 通过调用findById(Long id)方法来查找已删除的Todo对象。 将已删除的待办事项条目的ID作为方法参数传递。
- 通过执行以下步骤从数据库中删除待办事项条目:
- 通过调用DSLContext接口的delete(Table table)方法来创建新的DELETE语句,并指定要从todos表中删除信息。
- 通过调用DeleteWhereStep接口的where(Collection condition)方法,指定DELETE语句的WHERE子句。 确保DELETE语句删除ID为方法参数的待办事项。
- 通过调用Query接口的execute()方法执行 DELETE语句。
- 返回已删除的待办事项条目的信息。
JOOQTodoRepository类的相关部分如下所示:
import net.petrikainulainen.spring.jooq.todo.db.tables.records.TodosRecord;
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import static net.petrikainulainen.spring.jooq.todo.db.tables.Todos.TODOS;
@Repository
public class JOOQTodoRepository implements TodoRepository {
private final DSLContext jooq;
//The constructor is omitted for the sake of clarity
@Transactional
@Override
public Todo delete(Long id) {
Todo deleted = findById(id);
int deletedRecordCount = jooq.delete(TODOS)
.where(TODOS.ID.equal(id))
.execute();
return deleted;
}
}
第4.3.5节。 jOOQ参考手册的DELETE语句提供了有关从数据库中删除数据的其他信息。
让我们继续前进,找出如何更新现有待办事项的信息。
更新现有的Todo条目
TodoRepository接口的公共Todo update(Todo todoEntry)方法将更新现有todo条目的信息。 我们可以通过执行以下步骤来实现此方法:
- 将update()方法添加到存储库类中,并使用@Transactional注释对方法进行注释。 这样可以确保UPDATE语句在读写事务中执行。
- 通过调用DateTimeService接口的getCurrentTimestamp()方法获取当前日期和时间。
- 通过执行以下步骤来更新待办事项的信息:
- 通过调用DSLContext接口的update(Table table)方法来创建新的UPDATE语句,并指定您要更新从todos表中找到的信息。
- 通过调用UpdateSetStep接口的set(Field field,T value)方法来设置新的描述,修改时间和标题。
- 通过调用UpdateWhereStep接口的where(Collection condition)方法,指定UPDATE语句的WHERE子句。 确保UPDATE语句更新待办事项条目,该待办事项条目是从作为方法参数给出的Todo对象中找到的。
- 通过调用Query接口的execute()方法执行UPDATE语句。
- 通过调用findById()方法获取更新的待办事项条目的信息。 将更新的待办事项条目的ID作为方法参数传递。
- 返回更新的待办事项条目的信息。
JOOQTodoRepository类的相关部分如下所示:
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Timestamp;
import static net.petrikainulainen.spring.jooq.todo.db.tables.Todos.TODOS;
@Repository
public class JOOQTodoRepository implements TodoRepository {
private final DateTimeService dateTimeService;
private final DSLContext jooq;
//The constructor is omitted for the sake of clarity.
@Transactional
@Override
public Todo update(Todo todoEntry) {
Timestamp currentTime = dateTimeService.getCurrentTimestamp();
int updatedRecordCount = jooq.update(TODOS)
.set(TODOS.DESCRIPTION, todoEntry.getDescription())
.set(TODOS.MODIFICATION_TIME, currentTime)
.set(TODOS.TITLE, todoEntry.getTitle())
.where(TODOS.ID.equal(todoEntry.getId()))
.execute();
return findById(todoEntry.getId());
}
}
- 第4.3.4节。 jOOQ参考手册的UPDATE语句提供了有关更新存储在数据库中的信息的其他信息。
- 如果您使用的是Firebird或PostgreSQL数据库,则可以在update语句中使用RETURNING子句 (并避免使用额外的select子句)。
就这些了。 让我们总结一下我们从此博客文章中学到的知识。
摘要
现在,我们已经为待办事项实现了CRUD操作。 本教程教会了我们三件事:
- 我们了解了如何以不妨碍我们为示例应用程序编写自动测试的方式获取当前日期和时间。
- 我们了解了如何确保jOOQ执行的所有数据库查询都在事务内执行。
- 我们学习了如何使用jOOQ API创建INSERT , SELECT , DELETE和UPDATE语句。
本教程的下一部分描述了如何向示例应用程序添加支持排序和分页的搜索功能。
- 可以在Github上获得此博客文章的示例应用程序(尚未实现前端)。
翻译自: https://www.javacodegeeks.com/2014/04/using-jooq-with-spring-crud.html
jooq中record