Spring Data Jdbc如何连接表

在本文中,我们将研究Spring Data Jdbc如何构建sql查询来检索相关实体。



该帖子是为新手程序员设计的,不包含任何超级棘手的内容。





我邀请所有人参加在线课程“ 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.convertDefaultDataAccessStrategy我们已经熟悉了此类,现在我们对该方法很感兴趣。



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中联接表。

原则上,尽管存在一些非关键性的限制,但现在可用的功能非常适合解决实际问题。



示例的全文可以在这里找到



这是这篇文章视频版本






All Articles