2015-12-08 49 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中使用這個列表?我是否需要爲每個字段指定一個布爾參數,在提取實體時可以包含/排除? –