如何将联接 table 中的嵌入式 ID 的关系公开为与 Spring 数据 Rest 的链接

How to expose relations of an embedded id in a join table as links with Spring Data Rest

简介

假设在带有 Spring Data Rest 模块的 Spring 启动应用程序中有两个主要实体(例如 StudentLegalGuardian)。它们通过一个“关联实体”(例如 Guardianship)连接,该实体由一个嵌入式 ID(例如 GuardianshipId)标识。此外,这个嵌入的 id 包含与两个主要实体的关系(不是主要实体的 id - 实体本身)。

// The Main entities

@Entity
public class Student extends AbstractPersistable<Long> {

  private String name;
  
  @OneToMany(mappedBy = "guardianshipId.student")
  private List<Guardianship> guardianships;
  
  // getters and setters
  
}

@Entity
public class LegalGuardian extends AbstractPersistable<Long> {

  private String name;
  
  @OneToMany(mappedBy = "guardianshipId.legalGuardian")
  private List<Guardianship> guardianships;
  
  // getters and setters
  
}

// The association entity

@Entity
public class Guardianship implements Serializable {

  @EmbeddedId
  private GuardianshipId guardianshipId;
  
  private String name;
  
  // getters, setters, equals and hashCode

  @Embeddable
  public static class GuardianshipId implements Serializable {

    @ManyToOne
    private Student student;
    
    @ManyToOne
    private LegalGuardian legalGuardian;
    
    // getters, setters, equals and hashCode

  }

}

对于所有这些实体,存在单独的存储库:

要通过 REST 通过 id 查询 GuardianshipRepository 的单个 Guardianships,还实现了 BackendIdConverter(这样 id 看起来像 {studentId} _{legalGuardianId}).

如果请求关联实体的存储库,默认情况下嵌入的 id 本身(及其属性)不会被序列化,因此响应如下所示:

$ curl "http://localhost:8080/guardianships/1_2"
{
  "name" : "Cool father",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    },
    "guardianship" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    }
  }
}

Quesion/Problem

必须做什么,以便响应包含 链接 到在嵌入式 ID 中定义的实体,如下所示:

$ curl "http://localhost:8080/guardianships/1_2"
{
  "name" : "Cool father",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    },
    "guardianship" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    },
    "student" : {
      "href" : "http://localhost:8080/guardianships/1_2/student"
    },
    "legalGuardian" : {
      "href" : "http://localhost:8080/guardianships/1_2/legalGuardian"
    }
  }
}

(天真无成)Attempt/Try

第一个想法是通过委托嵌入的 id 使嵌套关系可访问:

@Entity
public class Guardianship implements Serializable {

  @EmbeddedId
  private GuardianshipId guardianshipId;
  
  public Student getStudent() { return guardianshipId.getStudent(); }
  
  public LegalGuardian getLegalGuardian() { return guardianshipId.getLegalGuardian(); }
  
  // the same as before

}

但是这样做,两个实体都被完全序列化并且响应如下所示:

$ curl "http://localhost:8080/guardianships/1_2"
{
  "name" : "Cool father",
  "student" : {
    "name" : "Hans",
    "new" : false
  },
  "legalGuardian" : {
    "name" : "Peter",
    "new" : false
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    },
    "guardianship" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    }
  }
}

为了完整示例,我创建了一个可执行文件 sample project

经过一番搜索,我发现了两种可能的方法来将 ID 关系公开为链接:

1.提供 RepresentationModelProcessor

实施 RepresentationModelProcessor 让我可以将自定义链接添加到响应表示。

@Component
public class GuardianshipProcessor
    implements RepresentationModelProcessor<EntityModel<Guardianship>> {

  @Autowired
  private RepositoryEntityLinks repositoryEntityLinks;

  @Override
  public EntityModel<Guardianship> process(EntityModel<Guardianship> model) {
    Link studentLink = repositoryEntityLinks.linkToItemResource(Student.class,
        model.getContent().getGuardianshipId().getStudent().getId());
    model.add(studentLink);
    Link legalGuardianLink = repositoryEntityLinks.linkToItemResource(LegalGuardian.class,
        model.getContent().getGuardianshipId().getLegalGuardian().getId());
    model.add(legalGuardianLink);
    return model;
  }

}
$ curl "http://localhost:8080/guardianships/1_2"
{
  "name" : "Cool father",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    },
    "guardianship" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    },
    "student" : {
      "href" : "http://localhost:8080/students/1"
    },
    "legalGuardian" : {
      "href" : "http://localhost:8080/legalGuardians/2"
    }
  }
}

临:

  • 完全匹配所需的响应表示

缺点:

  • 更多的关联 classes 导致更多的实现 RepresentationModelProcessor 做或多或少相同的事情

2。配置 RepositoryRestConfiguration 以公开 ID 的

默认情况下,ID 不会被 Spring Data Rest 公开,虽然主题是关于嵌入式 ID,但这些也是 ID。此行为可由 class 配置 class。

@Configuration
public class RepositoryConfig implements RepositoryRestConfigurer {

  @Override
  public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
    config.exposeIdsFor(Guardianship.class);
  }
  
}
$ curl "http://localhost:8080/guardianships/1_2"
{
  "guardianshipId" : {
    "_links" : {
      "student" : {
        "href" : "http://localhost:8080/students/1"
      },
      "legalGuardian" : {
        "href" : "http://localhost:8080/legalGuardians/2"
      }
    }
  },
  "name" : "Cool father",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    },
    "guardianship" : {
      "href" : "http://localhost:8080/guardianships/1_2"
    }
  }
}

临:

  • 少“执行”

缺点:

  • (以这种形式)与原始所需的响应表示不完全匹配(请参阅链接周围的 guardianshipId-wrapper)

编辑

对于方法二:要公开使用嵌入式(复合)ID 的实体的所有 ID,可以采用如下方式:

@Configuration                                                                                
public class RepositoryRestConfig implements RepositoryRestConfigurer {                       
                                                                                              
  @Autowired                                                                                  
  Repositories repositories;                                                                  
                                                                                              
  @Override                                                                                   
  public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {      
    repositories.forEach(repository -> {                                                      
      Field embeddedIdField =                                                                 
          ReflectionUtils.findField(repository, new AnnotationFieldFilter(EmbeddedId.class)); 
      if (embeddedIdField != null) {                                                          
        config.exposeIdsFor(repository);                                                      
      }                                                                                       
    });                                                                                       
  }                                                                                           
                                                                                              
}