指定服务返回的字段的最佳方法

时间:2015-12-08 21:02:10

标签: java java-ee jboss wildfly wildfly-9

我们正在使用Java EE 7和WildFly 9来开发移动/ Web应用程序的自定义后端。后端是一个经典的3层系统,具有通信逻辑(JAX-RS),业务逻辑(会话EJB)和持久层(Hibernate)。

业务逻辑层由一组服务组成,每个服务由接口和EJB实现定义。我们假设

public interface IPostService {
    List<PostDTO> getAllPosts();
}

@Stateless
public class PostService implements IPostService {
    List<PostDTO> getAllPosts(){
    // retrieving my Posts through Hibernate
    }

具有

public class PostDTO {

    private Long id;
    private String title;
    // UserDTO is a VEEERY big object
    private UserDTO author;

    // getters and setters
}

我们假设有时客户只对帖子idtitle感兴趣。 API端点将接收一个查询参数,其中包含要获取的字段列表。因此,JSON序列化的DTO应仅包含帖子idtitle。目标是避免不必要的处理,以便在不需要时加载非常大的UserDTO对象。

天真的解决方案是向List<String> desiredFields添加自定义getAllPosts()参数。这并不能说服我,因为我们需要将此参数添加到几乎每个服务方法中。

这样做的最佳做法是什么?是否有针对此目的的Java EE对象?

2 个答案:

答案 0 :(得分:0)

通过直接返回一个唯一的模型类实例,假设 Post 而不是PostDTO,结合JPA和JAXB注释,您可以从@XmlTransient和默认延迟加载中受益,以避免查询用户不需要时,持久层中的实体,以及从RESTful Web服务层隐藏此属性。

我个人用它来做很多事情,因为它通过减少层和映射(entity / dto)来简化应用程序代码。有关详细信息,请参阅these slides

我认为它非常适合CRUD操作,它是一个纯Java EE 7解决方案。无需使用额外的库。

Post.java看起来像这样:

import javax.persistence.*;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

@Entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @XmlTransient
    @ManyToOne
    private User persistedAuthor;

    @Transient
    private User author;

    // + Getters and Setters ...

}

PROS:

  • 没有DTO /实体映射来测试,编码和维护
  • 只有一个模型层可以放置持久性/业务/验证规则
  • Java EE 7解决方案,无需使用额外的库

CONS:

  • 您的模型与JPA相关联。因此,如果您更改持久性解决方案并且不允许管理多个不同的持久层,则需要对其进行修改

修改

有时您想要获取作者,假设您在REST操作中将示例的QueryParam fetchAuthor设置为true / false,则需要在该模型对象中添加额外的JPA @Transient属性,例如 author 并在需要时初始化它。所以它是Post java类中的额外映射,但是你保持上面提到的优点的好处。要初始化author属性,您只需使用getPersistedAuthor()返回的值设置它(这将触发查询以获取延迟加载的持久作者)。

答案 1 :(得分:0)

我的回答做了一些额外的假设:

  • 使用JPA 2.1,您可以使用实体图来有条件地获取实体的部分内容,无论是懒惰还是渴望。
  • 将JAX-RS与Jackson JSON提供程序一起使用,您可以使用@JsonView执行相同的操作,将对象渲染为JSON。

请考虑以下示例:用户具有一组角色。默认情况下角色是懒惰的,不需要用JSON渲染。但在某些情况下,您希望它们能够被提取,因为您希望它们以JSON格式呈现。

@Entity
@NamedQueries({
    @NamedQuery(name = "User.byName", query = "SELECT u FROM User u WHERE u.name = :name"),
    @NamedQuery(name = "Users.all", query = "SELECT u FROM User u ORDER BY u.name ASC")
})
@NamedEntityGraph(name = "User.withRoles", attributeNodes = {
    @NamedAttributeNode("roles") // make them fetched eager
})
public class User implements Serializable {

    public static interface WithoutRoles {}
    public static interface WithRoles extends WithoutRoles {}

    @Id
    private Long id;

    @Column(unique = true, updatable = false)
    private String name;

    @ManyToMany // fetched lazy by default
    @JoinTable(/* ... */)
    private Set<Role> roles;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // include in JSON only when "@JsonView(WithRoles.class)" is used:
    @JsonView(WithRoles.class)
    public Set<Role> getRoles() {
        if (roles == null)
            roles = new HashSet<>();
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

}

@Entity
public class Role implements Serializable {
    /* ... */
}

这里是加载用户的代码,有或没有角色:

public User getUser(String name, boolean withRoles) {
    TypedQuery<User> query = entityManager.createNamedQuery("User.byName", User.class)
        .setParameter("name", name);

    if (withRoles) {
        EntityGraph<User> graph = (EntityGraph<User>) entityManager.createEntityGraph("User.withRoles");
        query.setHint("javax.persistence.loadgraph", graph);
    }

    try {
        return query.getSingleResult();
    } catch (NoResultException e) {
        return null;
    }
}

public List<User> getAllUsers() {
    return entityManager.createNamedQuery("Users.all", User.class)
        .getResultList();
}

现在是REST资源:

@RequestScoped @Path("users")
public class UserResource {

    private @Inject UserService userService;

    // user list - without roles
    @GET @Produces(MediaType.APPLICATION_JSON)
    @JsonView(User.WithoutRoles.class)
    public Response getUserList() {
        List<User> users = userService.getAllUsers();
        return Response.ok(users).build();
    }

    // get one user - with roles
    @GET @Path("{name}") @Produces(MediaType.APPLICATION_JSON)
    @JsonView(User.WithRoles.class)
    public Response getUser(@PathParam("name") String name) {
        User user = userService.getUser(name, true);
        if (user == null)
            throw new NotFoundException();

        return Response.ok(user).build();
    }

}

因此,在持久性方面(JPA,Hibernate),您可以使用延迟提取来阻止加载实体的部分,并且在Web层(JAX-RS,JSON)上,您可以使用@JsonView来决定什么部件应在实体的(反)序列化中处理。