简体   繁体   English

如何忽略 Spring 数据 JPA 投影映射中的字段

[英]How to ignore field from Spring Data JPA projection mapping

I'm building a REST API for a Q&A web application.我正在为 Q&A web 应用程序构建 REST API。 It has communities where each community is defined by an id, name, description, members, and others.它有社区,其中每个社区都由 id、名称、描述、成员和其他人定义。 Right now, I have an endpoint for fetching the given community data /api/v1/communities/{id} .现在,我有一个端点来获取给定的社区数据/api/v1/communities/{id}

The response looks like this响应看起来像这样

 {
    "name": "VIP",
    "id": 5,
    "displayName": "VIP",
    "subtopics": [
        "Sports"
    ],
    "primaryTopic": "Gaming",
    "about": "",
    "isPublic": false,
    "isPrivate": true,
    "isRestricted": false
}

The response is a DTO that's mapped automatically using Spring Data Projection.响应是使用 Spring 数据投影自动映射的 DTO。 Now, I want to add a new field to the DTO.现在,我想向 DTO 添加一个新字段。 The field called isMember and it should store whether the currently authenticated user is a member of the given community.名为isMember的字段,它应该存储当前经过身份验证的用户是否是给定社区的成员。

public interface CommunityResponse {
    Long getId();

    String getName();

    String getDisplayName();

    String getAbout();

    @Value("#{target.primaryTopic.getDisplayName()}")
    String getPrimaryTopic();

    @Value("#{target.getSubtopicsDisplayNames()}")
    Set<String> getSubtopics();

    @Value("#{target.isPublic()}")
    @JsonProperty("isPublic")
    boolean isPublic();

    @Value("#{target.isPrivate()}")
    @JsonProperty("isPrivate")
    boolean isPrivate();

    @Value("#{target.isRestricted()}")
    @JsonProperty("isRestricted")
    boolean isRestricted();
   
    // boolean isMember();

}

I thought about using Class-based projection.我考虑过使用基于类的投影。 I converted the interface into a class and added the isMember field to the DTO and called setIsMember() inside the service class, but I got an error that says no property 'isMember' found for type 'CommunityResponse'我将接口转换为 class 并将isMember字段添加到 DTO 并在服务 class 中调用setIsMember() ,但我收到一个错误,提示no property 'isMember' found for type 'CommunityResponse'

As far as I understand, each field in a projection class or interface must either share the same name as a field in the entity or it should be derived from other fields using Spring's expression language.据我了解,投影 class 或接口中的每个字段必须与实体中的字段共享相同的名称,或者应该使用 Spring 的表达式语言从其他字段派生。

The problem is that isMember is not a field from the entity and I don't know how to derive it using Spring's expression language.问题是isMember不是来自实体的字段,我不知道如何使用 Spring 的表达式语言派生它。 The logic for initializing isMember looks like this:初始化isMember的逻辑如下所示:

       public boolean isMember(Long communityId, Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        return userAccountRepository.findByEmail(userDetails.getUsername()).map(user -> {
            return user.getJoinedCommunities().stream().anyMatch(community -> community.getId() == communityId);
        }).orElse(false);
    }

Other classes其他类

UserAccount.java

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserAccount {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    @NotBlank(message = "Firstname is required")
    private String firstname;
    @NotBlank(message = "Lastname is required")
    private String lastname;
    @NotBlank(message = "Username is required")
    private String username;
    @NotBlank(message = "Email is required")
    private String email;
    @NotBlank(message = "Password is required")
    private String hashedPassword;
    private Instant creationDate;
    private Boolean activated;
    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "members")
    private Set<Community> joinedCommunities;
}

Community.java

@Entity
@Table(name = "community")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Community {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Enumerated(EnumType.STRING)
    private CommunityType type;

    @Column(unique = true)
    private String name;
    private String displayName;

    private String about;

    @Enumerated(EnumType.STRING)
    private Topic primaryTopic;

    @CollectionTable(name = "subtopic")
    @ElementCollection(targetClass = Topic.class)
    @Enumerated(EnumType.STRING)
    @Column(name = "topic")
    private Set<Topic> subtopicSet;

    @ManyToMany
    @JoinTable(name = "community_members",
            joinColumns = { @JoinColumn(name = "community_id") },
            inverseJoinColumns = { @JoinColumn(name = "member_id") })
    private Set<UserAccount> members;


    public String getDisplayName() {
        return displayName;
    }

    public boolean isPublic() {
        return type == CommunityType.PUBLIC;
    }

    public boolean isPrivate() {
        return type == CommunityType.PRIVATE;
    }

    public boolean isRestricted() {
        return type == CommunityType.RESTRICTED;
    }

    public Set<String> getSubtopicsDisplayNames() {
        return getSubtopicSet().stream().map(Topic::getDisplayName).collect(Collectors.toSet());
    }

}

CommunityRepository.java

public interface CommunityRepository extends JpaRepository<Community, Long> {
    Optional<CommunityResponse> findCommunityById(Long id);

    List<CommunityResponse> findCommunityResponseBy();
}

I thought about using Class-based projection.我考虑过使用基于类的投影。 I converted the interface into a class and added the isMember field to the DTO我将接口转换为 class 并将 isMember 字段添加到 DTO

You could try to derive isMember within JPQL query constructing class-based projection:您可以尝试在构建基于类的投影的 JPQL 查询中派生isMember

@Query("select new com.my.project.DTO(c.id, c.name, (:user member of c.members)) from Community c ...")
List<CommunityResponse> findCommunityResponseBy(UserAccount user);

Alternatively, in interface-based projection you could try或者,在基于界面的投影中,您可以尝试

@Value("#{target.members.contains(#user)}")
boolean isMember;

where #user is the argument of findBy method.其中#userfindBy方法的参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM