【问题标题】:Handling Jackson Parent-Child Serialization处理 Jackson 父子序列化
【发布时间】:2014-07-15 03:56:59
【问题描述】:

我在我的项目中使用 Spring MVC(对此很陌生)、Hibernate 和 Jackson,并使用 JSON 在服务器和客户端之间进行数据交换。

我有几个具有 ManyToOne / OneToMany 关系的类“Employee”和“Address”。

我遇到了循环引用错误,但是,我能够使用 @JsonManagedReference 和 @JsonBackReference 解决这个问题。

但问题是,在序列化期间(我正在查询数据库以获取所有员工),Jackson 完全忽略了 Address 属性并仅序列化 3 个字段(其他字段已被特别忽略,您可以在代码中看到)。

这是返回的 JSON

[
    {
        "id": 1,
        "name": "xxx",
        "age": 100
    },
    {
        "id": 2,
        "name": "yyy",
        "age": 100
    }
]

员工类:

@Entity
@Table(name = "e_employee", catalog = "emploman")
public class Employee implements java.io.Serializable {

    private int id;

    private String name;

    private int age;

    private Address address;

    private String modifiedBy;

    private Date modifiedTime;

    private transient int addressId;

    public Employee() {
    }

    public Employee(int id, String name, int age, Address address, String modifiedBy, Date modifiedTime) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
        this.modifiedBy = modifiedBy;
        this.modifiedTime = modifiedTime;
    }

    public Employee(String name, int age, Address address, String modifiedBy, Date modifiedTime) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.modifiedBy = modifiedBy;
        this.modifiedTime = modifiedTime;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Column(name = "name", nullable = false, length = 100)
    public String getName() {
        return name;
    }

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

    @Column(name = "age", nullable = false)
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "address", nullable = false)
    @JsonBackReference("employee-address")
    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Column(name = "modified_by", nullable = false, length = 50)
    @JsonIgnore
    public String getModifiedBy() {
        return modifiedBy;
    }

    @JsonIgnore
    public void setModifiedBy(String modifiedBy) {
        this.modifiedBy = modifiedBy;
    }

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "modified_time", nullable = false, length = 19)
    @JsonIgnore
    public Date getModifiedTime() {
        return modifiedTime;
    }

    @JsonIgnore
    public void setModifiedTime(Date modifiedTime) {
        this.modifiedTime = modifiedTime;
    }

    @JsonIgnore
    @Transient
    public int getAddressId() {
        return addressId;
    }

    @JsonIgnore    
    public void setAddressId(int addressId) {
        this.addressId = addressId;
    }

}

地址类:

@Entity
@Table(name = "e_address", catalog = "emploman")
public class Address implements java.io.Serializable {

    private int id;

    private String country;

    private String state;

    private String city;

    private String streetAddress;

    private String pinCode;

    private String modifiedBy;

    private Date modifiedTime;

    private Set<Employee> employees;

    public Address() {
    }

    public Address(int id, String country, String state, String city, String streetAddress, String pinCode, String modifiedBy, Date modifiedTime, Set<Employee> employees) {
        this.id = id;
        this.country = country;
        this.state = state;
        this.city = city;
        this.streetAddress = streetAddress;
        this.pinCode = pinCode;
        this.modifiedBy = modifiedBy;
        this.modifiedTime = modifiedTime;
        this.employees = employees;
    }

    public Address(String country, String state, String city, String streetAddress, String pinCode, String modifiedBy, Date modifiedTime, Set<Employee> employees) {
        this.country = country;
        this.state = state;
        this.city = city;
        this.streetAddress = streetAddress;
        this.pinCode = pinCode;
        this.modifiedBy = modifiedBy;
        this.modifiedTime = modifiedTime;
        this.employees = employees;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Column(name = "country", nullable = false, length = 100)
    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    @Column(name = "state", nullable = false, length = 100)
    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    @Column(name = "city", nullable = false, length = 100)
    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Column(name = "street_address", nullable = false, length = 500)
    public String getStreetAddress() {
        return streetAddress;
    }

    public void setStreetAddress(String streetAddress) {
        this.streetAddress = streetAddress;
    }

    @Column(name = "pincode", nullable = false, length = 15)
    public String getPinCode() {
        return pinCode;
    }

    public void setPinCode(String pinCode) {
        this.pinCode = pinCode;
    }

    @Column(name = "modified_by", nullable = false, length = 50)
    public String getModifiedBy() {
        return modifiedBy;
    }

    public void setModifiedBy(String modifiedBy) {
        this.modifiedBy = modifiedBy;
    }

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "modified_time", nullable = false, length = 19)
    public Date getModifiedTime() {
        return modifiedTime;
    }

    public void setModifiedTime(Date modifiedTime) {
        this.modifiedTime = modifiedTime;
    }

    @OneToMany(mappedBy = "address", fetch = FetchType.LAZY)
    @JsonManagedReference(value = "employee-address")
    public Set<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(Set<Employee> employees) {
        this.employees = employees;
    }

}

所以,基本上,我期待的 json 响应如下所示:

[
    {
        "id": 1,
        "name": "xxx",
        "age": 100,
        "address": {
            "country": "xxx",
            "city": "abc"
        }
    }
]

有人可以帮忙吗?

更新 1

我已尝试从数据库中获取地址,并且已成功获取与该地址相关的员工

[
    {
        "id": 1,
        "country": "xxx",
        "state": "yyy",
        "city": "zzz",
        "streetAddress": "abc",
        "pinCode": "12345",
        "modifiedBy": "xxx",
        "modifiedTime": 1400930509000,
        "employees": [
            {
                "id": 2,
                "name": "xxx",
                "age": 190
            },
            {
                "id": 1,
                "name": "xxx",
                "age": 200
            }
        ]
    }
]

【问题讨论】:

    标签: java json spring hibernate jackson


    【解决方案1】:

    可能导致此行为的一个问题是这是一个延迟获取的字段。并且事务可能在序列化为 json 之前完成(取决于您的代码)。实体可能在 Jackson 获取时已分离,getter 将返回 null(或空列表,不确定)。您可以通过在实体被分离之前使字段预先获取或调用 getter 来检查这一点(当事务仍处于活动状态时)。

    在我们的例子中,它是一个容器管理的 EJB 事务,并且映射器是从 Jersey servlet 调用的。

    【讨论】:

    • 在这种情况下,至少字段“地址”应该在 JSON 中可用,并且在我发布的情况下看不到空值。
    • 不幸的是,这就是我面临的问题......就延迟获取而言,我使用的是 OpenSessionInViewFilter,所以我认为在请求完成之前不会关闭会话...这与杰克逊有关,而不是 Hibernate 或 Spring,我可能缺少一些简单的注释,或者可能添加了额外的注释或其他东西!!
    【解决方案2】:

    谢谢大家。

    我能够通过在我的类(父类和子类)上使用 JsonIdentityInfo 以及一个名为 jackson-datatype-hibernate 的插件来解决此问题,通过该插件我启用了一个名为 FORCE_LAZY_LOADING 的功能

    如果对任何人有帮助,以下是上述两种配置的代码:

    如下创建一个类来启用jackson-datatype-hibernate

    public class HibernateAwareObjectMapper extends ObjectMapper {
    
        public HibernateAwareObjectMapper() {
            Hibernate4Module hbm = new Hibernate4Module();
            hbm.enable(Hibernate4Module.Feature.FORCE_LAZY_LOADING);
            registerModule(hbm);
        }
    }
    

    告诉Spring使用上面的ObjectMapper而不是Jackson提供的默认值:

    <mvc:annotation-driven>
            <mvc:message-converters>
                <!-- Use the HibernateAware mapper instead of the default -->
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                    <property name="objectMapper">
                        <bean class="com.adwitiya.o2plus.utilities.HibernateAwareObjectMapper" />
                    </property>
                </bean>
            </mvc:message-converters>
        </mvc:annotation-driven>
    

    至于 JsonIdentityInfo,下面是代码。注意 JsonIgnoreProperties 的使用(我主要将它用于所有集合,因为它创建了一个递归大树,我想避免这种情况)

    @Entity
    @Table(name = "o2_branch", catalog = "o2plus"
    )
    @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
    @JsonIgnoreProperties(value = {"active", "modifiedBy", "modifiedTimestamp", "members", "staff"})
    public class Branch implements java.io.Serializable {
    
        private Long id;
        private Address address;
        private String name;
        private String contactNumber;
        private Integer capacity;
        private String manager;
        private boolean active;
        private String modifiedBy;
        private Date modifiedTimestamp;
        private Set<Member> members = new HashSet<Member>(0);
        private Set<Staff> staff = new HashSet<Staff>(0);
    

    希望能帮助更多人。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-23
      • 1970-01-01
      • 2020-01-25
      • 2012-08-31
      • 1970-01-01
      • 2020-07-31
      • 2012-07-29
      • 1970-01-01
      相关资源
      最近更新 更多