该帖子是为新手程序员设计的,不包含任何超级棘手的内容。
我邀请所有人参加在线课程“ Java开发人员”的演示日。专业“。作为活动的一部分,我将向您详细介绍该课程计划,并回答您的问题。
使用了Hibernate之类的解决方案的很大一部分,因为 这对于处理嵌套对象非常方便。
例如,有一个RecordPackage类,该类的字段之一是子(或嵌套)对象的集合:记录。
如果使用Jdbc,则必须编写许多例程代码。很少有人喜欢它,这就是他们使用休眠模式的部分原因。
使用Hibernate,您可以一次调用一个方法以获取带有其所有子记录的RecordPackage。
一方面,我想使用一种方法来获取整个对象,另一方面,我不想弄乱休眠的怪物。
Spring Data Jdbc使您能够充分利用这两个世界(或至少可以接受的东西)。
考虑两种情况:
- 一对多关系
- 一对一关系
在实践中最经常遇到的就是这些连接。
示例的完整代码可以在GitHub上找到,这里我只给出最低限度的内容。
首先,值得注意的是,Spring Data Jdbc并不是解决任何问题的神奇工具。它当然有其缺点和局限性。
但是,对于许多典型任务,这是一个非常合适的解决方案。
一对多关系
作为一个真实的示例,您可以考虑:一些数据包的标头以及此数据包中包含的数据线。例如,文件是一个包,文件行是进入该包的数据行。
表的结构如下:
create table record_package
(
record_package_id bigserial not null
constraint record_package_pk primary key,
name varchar(256) not null
);
create table record
(
record_id bigserial not null
constraint record_pk primary key,
record_package_id bigint not null,
data varchar(256) not null
);
alter table record
add foreign key (record_package_id) references record_package;
两个表:(
record_package
某个数据包的标题)和record
(数据包中包含的记录)。
此关系如何在Java代码中显示:
@Table("record_package")
public class RecordPackage {
@Id
private final Long recordPackageId;
private final String name;
@MappedCollection(idColumn = "record_package_id")
private final Set<Record> records;
….
}
在这里,我们有兴趣定义一对多关系。这是使用注解编码的
@MappedCollection
。
该注释具有两个参数:
idColumn-建立连接的字段
; keyColumn-排序子表中的记录的字段。
此顺序值得一提。在此示例中,子记录将以什么顺序插入记录表对我们来说都没有关系,但是在某些情况下,这可能很重要。对于这种排序,记录表将具有一个类似于record_no的字段,并且需要将此字段写入MappedCollection批注的keyColumn中。当执行插入时,Spring Data Jdbc将生成该字段的值。除注释外,“设置
<
记录”>
将需要替换为“列表”<
Record >
,这是很合逻辑且可以理解的。在形成选择时,将考虑明确指定的子行顺序,但是稍后我们将返回。
因此,我们已经确定了连接,并准备尝试一下。
我们创建相关实体并从基础中获取它们:
var record1 = new Record("r1");
var record2 = new Record("r2");
var record3 = new Record("r3");
var recordPackage = new RecordPackage( "package", Set.of(record1, record2, record3));
var recordPackageSaved = repository.save(recordPackage);
var recordPackageLoaded = repository.findById(recordPackageSaved.getRecordPackageId());
请注意,我们只需要调用一个方法
repository.findById
即可获得RecordPackage
具有填充记录集合的实例。
当然,我们对执行哪种SQL查询以获取嵌套的收集记录感兴趣。
与Hibernate相比,Spring Data Jdbc具有简单性。可以很容易地调试它以揭示要点。
在org.springframework.data.jdbc.core.convert包中进行了一些调查之后,我们找到了DefaultDataAccessStrategy类。此类负责根据类信息生成SQL查询。现在在这个课上我们对方法感兴趣
可迭代的<Object> findAllByPath
更确切地说是:
String findAllByProperty = sql(actualType)
.getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered());
从内部缓存中检索所需的SQL查询。
在我们的例子中,它看起来像这样:
SELECT "record"."data" AS "data", "record"."record_id" AS "record_id", "record"."record_package_id" AS "record_package_id"
FROM "record"
WHERE "record"."record_package_id" = :record_package_id
一切都清晰可预测。
如果我们使用子表中记录的顺序会是什么样?显然,需要排序依据。
让我们继续到org.springframework.data.relational.core.mapping包的BasicRelationalPersistentProperty类。此类具有一种方法,该方法确定是否向查询中添加订单依据。
public boolean isOrdered() {
return isListLike();
}
和
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}
isCollectionLike验证我们确实有一个“集合”(包括数组)。
并且从条件!Set.class.isAssignableFrom(this.getType());开始。很明显,不是偶然使用Set,而是排除了不必要的排序。有一天,我们将有意使用List启用排序。
我认为一对多或多或少是清楚的,让我们继续下一个案例。
一对一关系
假设我们有这样的结构。
create table info_main
(
info_main_id bigserial not null
constraint info_pk primary key,
main_data varchar(256) not null
);
create table info_additional
(
info_additional_id bigserial not null
constraint additional_pk primary key,
info_main_id bigint not null,
additional_data varchar(256) not null
);
alter table info_additional
add foreign key (info_main_id) references info_main;
有一个表,其中包含有关某个对象的基本信息(info_main),还有其他信息(info_additional)。
如何用代码表示:
@Table("info_main")
public class InfoMain {
@Id
private final Long infoMainId;
private final String mainData;
@MappedCollection(idColumn = "info_main_id")
private final InfoAdditional infoAdditional;
…
}
乍一看,它看起来像第一个一对多的案例,但有所不同。这次,孩子实际上是一个对象,而不是前面的情况中的集合。
用于测试的代码如下所示:
var infoAdditional = new InfoAdditional("InfoAdditional");
var infoMain = new InfoMain("mainData", infoAdditional);
var infoMainSaved = repository.save(infoMain);
var infoMainLoaded = repository.findById(infoMainSaved.getInfoMainId());
让我们看看这次生成了哪个sql表达式。为此,我们将findById方法挖掘到以下位置:
包org.springframework.data.jdbc.core.convert类DefaultDataAccessStrategy。我们已经熟悉了此类,现在我们对该方法很感兴趣。
public <T> T findById(Object id, Class<T> domainType)
我们看到从缓存中检索到以下请求:
SELECT "info_main"."main_data" AS "main_data", "info_main"."info_main_id" AS "info_main_id", "infoAdditional"."info_main_id" AS "infoadditional_info_main_id", "infoAdditional"."additional_data" AS "infoadditional_additional_data", "infoAdditional"."info_additional_id" AS "infoadditional_info_additional_id"
FROM "info_main"
LEFT OUTER JOIN "info_additional" "infoAdditional"
ON "infoAdditional"."info_main_id" = "info_main"."info_main_id"
WHERE "info_main"."info_main_id" = :id
现在,左边的外部连接适合我们,但是如果不适合的话。我如何获得内部加入?
在org.springframework.data.jdbc.core.convert包中,类SqlGenerator的方法中创建函数join-s :
private SelectBuilder.SelectWhere selectBuilder(Collection<SqlIdentifier> keyColumns)
我们对此片段感兴趣:
for (Join join : joinTables) {
baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
}
如果需要联接表,则只有左外部联接有一个选项。
似乎内部联接还不能完成。
结论
我们已经介绍了两种最典型的案例,说明如何在Spring Data Jdbc中联接表。
原则上,尽管存在一些非关键性的限制,但现在可用的功能非常适合解决实际问题。
示例的全文可以在这里找到。
这是这篇文章的视频版本。