Jackson JSON序列化JPA实体

时间:2014-05-20 04:35:30

标签: jackson jpa-2.0 resteasy

我有一个JPA持久层,有许多@Entity类,它们有许多OneToMany和ManyToMany关系。 我想通过RestEasy将这些实体暴露给Jackson2作为序列化程序,将JSON作为REST服务。

我知道@JsonIdentityInfo用于解析循环引用。 问题在于我需要暴露实体字段的不同子集的不同REST服务。此外,我需要为集合公开不同级别的depts(OneToMany,OneToOne等)。 例如,对于这个简单的实体:

class User {
    Long id;
    String name;
    Company company;
}

class Company {
    Long id;
    String name;
    List<User> users;
    List<Product> products;
}

class Product {
    Long id;
    String name;
    List<User> users;
}

和这个REST服务:

class MyResource {
    User getUser() { //... }
    List<User> getUsers() { //... }
    Company getCompany() { //... }
    List<Company> getComanies() { //... }
}

在方法getUser()中,我需要返回包含内部Company对象的完整User对象的JSON。但该公司当然只需要包含他们的idname字段,而不是完整的用户列表。更重要的是内部公司JSON不得包含products!这是合乎逻辑的。如果我们收到用户,我们就不需要与该用户相关的公司产品。如果我们需要它们,我们将发送另一个REST请求。

但是在方法getCompany()中,我需要返回带有完整Company对象的JSON,包括User和Product对象的内部JSON数组。当然,这次User对象不需要为Company对象包含内部JSON。

出于这个原因,我无法使用@JsonIgnore。在一种情况下,我们需要一些领域而在另一种情况下我们不需要。

现在我想出了使用Jackson视图(@JsonView注释)的方法。我为每个MyResource getter提供了具有不同视图的View类。

public class Views {
    public static class User {}
    public static class Users {}
    public static class Company {}
    public static class Companies {}
    // etc...
}

和MyResoruce类为

class MyResource {
    @JsonView(Views.User.class)
    User getUser() { //... }
    @JsonView(Views.Users.class)
    List<User> getUsers() { //... }
    @JsonView(Views.Company.class)
    Company getCompany() { //... }
    @JsonView(Views.Companies.class)
    List<Company> getComanies() { //... }
}

并为每个实体提供一个MixIn类,每个字段都注释为

public abstract class UserMixIn {
    @JsonView({ Views.User.class, Views.Users.class, Views.Company.class, Views.Companies.class })
    public abstract Long getId();
    @JsonView({ Views.User.class, Views.Users.class, Views.Company.class, Views.Companies.class })
    public abstract String getName();
    @JsonView({ Views.User.class, Views.Users.class })
    public abstract Company getCompany();
}

public abstract class CompanyMixIn {
    @JsonView({ Views.Company.class, Views.Companies.class, Views.User.class, Views.Users.class })
    public abstract Long getId();
    @JsonView({ Views.Company.class, Views.Companies.class, Views.User.class, Views.Users.class })
    public abstract String getName();
    @JsonView({ Views.Company.class, Views.Companies.class })
    public abstract List<User> getUsers();
    @JsonView({ Views.Company.class, Views.Companies.class })
    public abstract List<Product> getProducts();
}

public abstract class ProductMixIn {
    @JsonView({ Views.Company.class, Views.Companies.class })
    public abstract Long getId();
    @JsonView({ Views.Company.class, Views.Companies.class })
    public abstract String getName();
    public abstract List<User> getUsers();
}

支持案例的复数,其中getUsers()并不需要每个用户的完整内部公司对象(性能)。 当然只有示例类。真正的课程更大更复杂。

我不喜欢这种方法,因为我担心将来它可能是一场噩梦(太多不可管理的观点)。也许有将JPA实体暴露为REST服务的通用方法?我相信这是一项相当普遍的任务。但无法找到关于其他人如何做到这一点的任何可理解的信息。也许是一些最佳实践。

1 个答案:

答案 0 :(得分:2)

您的服务层(以及您的REST控制器层)必须公开DTOs (Data transfer objects)而不是@Entity个对象。

示例:

对于服务1(专注于用户管理):

public class UserDto {
    private Long id;
    private String name;
    private CompanyDtoLight company;
}

public class CompanyDtoLight {
    private Long id;
    private String name;
}

服务2(专注于公司管理):

public class CompanyDto {
    private Long id;
    private String name;
    List<UserDtoLight > users;
    List<ProductDtoLight > products;
}

public class UserDtoLight {
    private Long id;
    private String name;
}

class ProductDtoLight {
    private Long id;
    private String name;
}

(您的DTO的命名是你的)

如何:

您需要Mappers将@Entity转换为DTO并将其转换为DTO。有些lib存在,如DozerMapStruct(还有很多其他的)。