对每个实体子类型使用不同的投影

Using different projection for each entity subtype

是否可以通过子类型和 Spring 数据 REST 定义不同的投影 使用关于 class 类型的最具体投影?

此问题已在 JIRA 问题 DATAREST-739 and also exists a merge commit but this not appears on official changelog 上公开,而且我没有找到任何文档或指南来解决当前版本的问题。

问题中使用的用例样本是:

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="type")
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
public abstract class Message implements Identifiable<UUID> { ... }

@Entity
@DiscriminatorValue("TEXT")
@JsonTypeName("TEXT")
public class TextMessage extends Message { ... }

@Entity
@DiscriminatorValue("TODO")
@JsonTypeName("TODO")
public class TodoMessage extends Message { Boolean isDone; }

@Projection(name = "summary", types = TodoMessage.class)
public class TodoMessageSummary { Boolean getIsDone(); }

@Projection(name = "summary", types = TextMessage.class)
public class TextMessageSummary { ... }

public interface MessageRepo extends JpaRepository<Message, UUID> { ... }

@RepositoryRestResource(excerptProjection = TodoMessageSummary.class)
public interface TodoMessageRepo extends JpaRepository<Message, UUID> { ... }

@RepositoryRestResource(excerptProjection = TextMessageSummary.class)
public interface TextMessageRepo extends JpaRepository<TextMessage, UUID> { ... }

第一期:如何定义MessageRepo的摘录投影,TodoMessage实体使用TodoMessageSummary,TextMessage使用TextMessageSummary?

第二期:如何为另一个有Message字段的实体定义投影?假设您有以下内容:

@Projection(name = "summary", types = Dashboard.class)
public class DashboardSummary {
  List<Message> getMessages();
}

已解决:

是的,这是可能的。我们的应用程序中有这样的 sub-types 结构。

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
public abstract class Vehicle {
  protected VehicleType type;
}

public class Plane extends Vehicle {
  private String title;
}

对他们的预测:

public interface VehicleProjection {
  String getType();
}

@Projection(name = "default", types = Plane.class)
public interface PlaneProjection extends VehicleProjection {
  String getTitle();
}

其余存储库:

@RepositoryRestResource(collectionResourceRel = "collectionName", path = "path",
        excerptProjection = VehicleProjection.class)
public interface RestVehicleRepository<T extends Vehicle> extends MongoRepository<T, String> {

}

我们还在配置中注册了这些投影。不确定您的情况是否需要它,因为我们对每个 sub-type:

有不止一个投影
@Configuration
public class CustomRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
 @Override
        public void configureRepositoryRestConfiguration(final RepositoryRestConfiguration config) {
          config.getProjectionConfiguration().addProjection(PlaneProjection.class,
                    "default", Plane.class);
  }
}

诀窍是在子类型投影中使用继承:

@Projection(name = "summary", types = Message.class)
public class MessageSummary { 
    @Value("#{target.getClass().getSimpleName()}")
    String getType();    
}

@Projection(name = "summary", types = TextMessage.class)
public class TextMessageSummary extends MessageSummary { ... }

@Projection(name = "summary", types = TodoMessage.class)
public class TodoMessageSummary extends MessageSummary {
     Boolean getIsDone();
}

Spring REST @RepositoryRestResource returns 使用具体子类型投影的消息数组(isDone 必须出现在 TodoMessage 实例中)

这个问题有点复杂,如果你需要将相同的扩展@RequestMapping变成Controller,为此我使用下一个snnipeed:

    Page<Message> results = repository.findAll(predicate, pageable);
    Converter<? super Message, ? extends MessageSummary> converter= l -> {
        if(l instanceof TextMessage){
            return projectionFactory.createProjection(TextMessageSummary.class,l);
        }
        else if(l instanceof TodoMessage){
            return projectionFactory.createProjection(TodoMessageSummary.class,l);
        }
        else { 
            return projectionFactory.createProjection(MessageSummary.class,l);
        }
    };
    Page<MessageSummary> projected =results.map(converter);
    return pagedAssembler.toResource(projected);

请注意,如果前端只需要 资源类型信息 用于只读目的(即 POST/PUT 使用具体的子类型端点),我认为这并不是真正需要使用的@JsonTypeInfo 因为在投影中使用 SpEL 可以更轻松、更灵活地获取此类型信息:

@Value("#{target.getClass().getSimpleName()}")
String getType();