2015-12-08 48 views
3

我们使用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对象是否有此目的?

+0

也许创建一个简单的NameId值对象,让Hibernate为你构建VO实例列表?假设你使用JPA:http://stackoverflow.com/questions/2355728/jpql-create-new-object-in-select-statement-avoid-or-embrace – Gimby

回答

0

通过直接返回一个独特的模型类的实例,让我们说代替PostDTO,结合JPA和JAXB注解,你可以从@XmlTransient和默认延迟加载受益,以避免在不必要的查询用户实体持久层,并从RESTful Web服务层隐藏此属性。

我personnaly使用这么做,因为它通过减少层和映射(实体/ 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 ... 

} 

优点:

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

缺点:

  • 您的模型依赖于JPA。所以,你需要的情况下,您更改持久性解决方案来修改这一点,它不允许管理多个不同的持久层

编辑

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

+0

纠正我,如果我错了,但以这种方式,我即使他们请求所有字段,也不会将'author'返回给客户端。 –

+0

你说得对。我已经更新了我的答案来处理这一点。最后请参阅*编辑*部分。 –

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来决定哪些部分应该在(反)序列化中处理的实体。

+0

非常详细的答案,谢谢!我仍然有一些疑虑。 API端点将接收带有要获取的字段列表的查询参数。如何在@JsonView中使用这个列表?我是否需要为每个字段指定一个布尔参数,在提取实体时可以包含/排除? –