Ta *_*Sas 375 java orm json spring-mvc jackson
在尝试将具有双向关联的JPA对象转换为JSON时,我不断获取
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
Run Code Online (Sandbox Code Playgroud)
我找到的只是这个线程,基本上建议避免双向关联.有没有人对这个春天的bug有一个解决方法?
------编辑2010-07-24 16:26:22 -------
Codesnippets:
业务对象1:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class Trainee extends BusinessObject {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "name", nullable = true)
private String name;
@Column(name = "surname", nullable = true)
private String surname;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<BodyStat> bodyStats;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<Training> trainings;
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
private Set<ExerciseType> exerciseTypes;
public Trainee() {
super();
}
... getters/setters ...
Run Code Online (Sandbox Code Playgroud)
业务对象2:
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class BodyStat extends BusinessObject {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "height", nullable = true)
private Float height;
@Column(name = "measuretime", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
private Date measureTime;
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
private Trainee trainee;
Run Code Online (Sandbox Code Playgroud)
控制器:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Controller
@RequestMapping(value = "/trainees")
public class TraineesController {
final Logger logger = LoggerFactory.getLogger(TraineesController.class);
private Map<Long, Trainee> trainees = new ConcurrentHashMap<Long, Trainee>();
@Autowired
private ITraineeDAO traineeDAO;
/**
* Return json repres. of all trainees
*/
@RequestMapping(value = "/getAllTrainees", method = RequestMethod.GET)
@ResponseBody
public Collection getAllTrainees() {
Collection allTrainees = this.traineeDAO.getAll();
this.logger.debug("A total of " + allTrainees.size() + " trainees was read from db");
return allTrainees;
}
}
Run Code Online (Sandbox Code Playgroud)
实习生DAO的JPA实施:
@Repository
@Transactional
public class TraineeDAO implements ITraineeDAO {
@PersistenceContext
private EntityManager em;
@Transactional
public Trainee save(Trainee trainee) {
em.persist(trainee);
return trainee;
}
@Transactional(readOnly = true)
public Collection getAll() {
return (Collection) em.createQuery("SELECT t FROM Trainee t").getResultList();
}
}
Run Code Online (Sandbox Code Playgroud)
persistence.xml中
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="RDBMS" transaction-type="RESOURCE_LOCAL">
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<property name="hibernate.archive.autodetection" value="class"/>
<property name="dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
<!-- <property name="dialect" value="org.hibernate.dialect.HSQLDialect"/> -->
</properties>
</persistence-unit>
</persistence>
Run Code Online (Sandbox Code Playgroud)
Kur*_*aki 565
您现在可以使用JsonIgnoreProperties来抑制属性的序列化(在序列化期间),或忽略处理读取的JSON属性(在反序列化期间).如果这不是您想要的,请继续阅读下面的内容.
(感谢As Zammel AlaaEddine指出这一点).
从Jackson 1.6开始,您可以使用两个注释来解决无限递归问题,而不会在序列化期间忽略getter/setter:@JsonManagedReference和@JsonBackReference.
说明
为了让Jackson运行良好,关系的两个方面之一不应该被序列化,以避免导致堆栈溢出错误的infite循环.
因此,Jackson采用了引用的前向部分(您Set<BodyStat> bodyStats在Trainee类中),并将其转换为类似json的存储格式; 这就是所谓的编组过程.然后,杰克逊寻找引用的Trainee trainee后半部分(即在BodyStat类中)并保持原样,而不是序列化它.这部分关系将在前向引用的反序列化(解组)期间重新构建.
您可以像这样更改您的代码(我跳过无用的部分):
业务对象1:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class Trainee extends BusinessObject {
@OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@Column(nullable = true)
@JsonManagedReference
private Set<BodyStat> bodyStats;
Run Code Online (Sandbox Code Playgroud)
业务对象2:
@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
public class BodyStat extends BusinessObject {
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="trainee_fk")
@JsonBackReference
private Trainee trainee;
Run Code Online (Sandbox Code Playgroud)
现在一切都应该正常运作.
如果你想要更多的信息,我在Keenformatics上写了一篇关于Json和Jackson Stackoverflow问题的文章,我的博客.
编辑:
你可以检查的另一个有用的注释是@JsonIdentityInfo:使用它,每当Jackson序列化你的对象时,它会为它添加一个ID(或你选择的另一个属性),这样它就不会每次都完全"扫描"它.当你在更多相互关联的对象之间有一个链循环时(例如:Order - > OrderLine - > User - > Order and over),这可能很有用.
在这种情况下,您必须要小心,因为您可能需要多次读取对象的属性(例如,在产品列表中包含更多共享同一卖家的产品),并且此注释会阻止您这样做.我建议总是看看firebug日志来检查Json响应,看看你的代码中发生了什么.
资料来源:
axt*_*avt 268
你可以@JsonIgnore用来打破这个循环.
小智 95
新注释@JsonIgnoreProperties解决了其他选项的许多问题.
@Entity
public class Material{
...
@JsonIgnoreProperties("costMaterials")
private List<Supplier> costSuppliers = new ArrayList<>();
...
}
@Entity
public class Supplier{
...
@JsonIgnoreProperties("costSuppliers")
private List<Material> costMaterials = new ArrayList<>();
....
}
Run Code Online (Sandbox Code Playgroud)
在这里查看.它就像在文档中一样工作:http:
//springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
Mar*_*cus 44
另外,使用Jackson 2.0+可以使用@JsonIdentityInfo.这对我的Hibernate类比工作更好的@JsonBackReference和@JsonManagedReference,其中有问题,对我来说,并没有解决问题.只需添加以下内容:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@traineeId")
public class Trainee extends BusinessObject {
@Entity
@Table(name = "ta_bodystat", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@bodyStatId")
public class BodyStat extends BusinessObject {
Run Code Online (Sandbox Code Playgroud)
它应该工作.
Sta*_*Man 19
此外,杰克逊1.6支持处理双向引用 ...这似乎是你正在寻找的(这篇博文也提到了这个功能)
截至2011年7月,还有" jackson-module-hibernate "可能在处理Hibernate对象的某些方面有所帮助,尽管不一定是特定的(需要注释).
小智 9
这对我来说非常好.在子类中添加注释@JsonIgnore,其中提到对父类的引用.
@ManyToOne
@JoinColumn(name = "ID", nullable = false, updatable = false)
@JsonIgnore
private Member member;
Run Code Online (Sandbox Code Playgroud)
现在有一个Jackson模块(用于Jackson 2)专门用于在序列化时处理Hibernate延迟初始化问题.
https://github.com/FasterXML/jackson-datatype-hibernate
只需添加依赖项(注意Hibernate 3和Hibernate 4有不同的依赖项):
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>2.4.0</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)
然后在初始化Jackson的ObjectMapper时注册模块:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
Run Code Online (Sandbox Code Playgroud)
文档目前不是很好.有关可用选项,请参阅Hibernate4Module代码.
对我而言 工作正常与杰克逊一起工作时解决Json Infinite Recursion问题
这是我在oneToMany和ManyToOne映射中所做的
@ManyToOne
@JoinColumn(name="Key")
@JsonBackReference
private LgcyIsp Key;
@OneToMany(mappedBy="LgcyIsp ")
@JsonManagedReference
private List<Safety> safety;
Run Code Online (Sandbox Code Playgroud)
非常重要:如果您使用 LOMBOK,请确保排除 Set、List 等集合的属性...
像这样:
@EqualsAndHashCode(exclude = {"attributeOfTypeList", "attributeOfTypeSet"})
Run Code Online (Sandbox Code Playgroud)
在我的情况下,改变关系就足够了:
@OneToMany(mappedBy = "county")
private List<Town> towns;
Run Code Online (Sandbox Code Playgroud)
到:
@OneToMany
private List<Town> towns;
Run Code Online (Sandbox Code Playgroud)
另一个关系保持原样:
@ManyToOne
@JoinColumn(name = "county_id")
private County county;
Run Code Online (Sandbox Code Playgroud)
对我而言,最好的解决方案是@JsonView针对每种情况使用并创建特定的过滤器。您也可以使用@JsonManagedReference和@JsonBackReference,但是,它仅是一种硬编码的解决方案,在这种情况下,所有者总是引用所有者,而从不相反。如果在另一个序列化方案中需要以不同的方式重新注释属性,则将无法执行此操作。
现在让我们使用两个班,Company并Employee在那里你有它们之间的循环依赖:
public class Company {
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
public class Employee {
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
Run Code Online (Sandbox Code Playgroud)
以及尝试使用ObjectMapper(Spring Boot)进行序列化的测试类:
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {
@Autowired
public ObjectMapper mapper;
@Test
public void shouldSaveCompany() throws JsonProcessingException {
Employee employee = new Employee();
Company company = new Company(employee);
employee.setCompany(company);
String jsonCompany = mapper.writeValueAsString(company);
System.out.println(jsonCompany);
assertTrue(true);
}
}
Run Code Online (Sandbox Code Playgroud)
如果运行此代码,您将获得:
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
Run Code Online (Sandbox Code Playgroud)
@JsonView使您可以使用过滤器并选择在序列化对象时应包括哪些字段。过滤器只是用作标识符的类引用。因此,让我们首先创建过滤器:
public class Filter {
public static interface EmployeeData {};
public static interface CompanyData extends EmployeeData {};
}
Run Code Online (Sandbox Code Playgroud)
请记住,过滤器是伪类,仅用于通过@JsonView注释指定字段,因此您可以根据需要创建任意多个。让我们来看一下它的作用,但是首先我们需要注释我们的Company类:
public class Company {
@JsonView(Filter.CompanyData.class)
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
Run Code Online (Sandbox Code Playgroud)
并更改“测试”以使序列化程序使用“视图”:
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {
@Autowired
public ObjectMapper mapper;
@Test
public void shouldSaveCompany() throws JsonProcessingException {
Employee employee = new Employee();
Company company = new Company(employee);
employee.setCompany(company);
ObjectWriter writter = mapper.writerWithView(Filter.CompanyData.class);
String jsonCompany = writter.writeValueAsString(company);
System.out.println(jsonCompany);
assertTrue(true);
}
}
Run Code Online (Sandbox Code Playgroud)
现在,如果您运行此代码,则可以解决“无限递归”问题,因为您已明确表示只想序列化使用注释的属性@JsonView(Filter.CompanyData.class)。
当到达中的company的后向引用时Employee,它将检查是否未注释并忽略序列化。您还具有强大而灵活的解决方案,可以选择要通过REST API发送的数据。
使用Spring,您可以使用所需的@JsonView过滤器注释REST Controllers方法,并将序列化透明地应用于返回的对象。
这是您需要检查的导入文件:
import static org.junit.Assert.assertTrue;
import javax.transaction.Transactional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.annotation.JsonView;
Run Code Online (Sandbox Code Playgroud)
我也遇到了同样的问题。我用的@JsonIdentityInfo是 的ObjectIdGenerators.PropertyGenerator.class发电机类型。
这就是我的解决方案:
@Entity
@Table(name = "ta_trainee", uniqueConstraints = {@UniqueConstraint(columnNames = {"id"})})
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Trainee extends BusinessObject {
...
Run Code Online (Sandbox Code Playgroud)
答案就是 @JsonIgnoreProperties。
使用这样的东西::
@OneToMany(mappedBy = "course",fetch=FetchType.EAGER)
@JsonIgnoreProperties("course")
private Set<Student> students;
Run Code Online (Sandbox Code Playgroud)
小智 5
您应该将@JsonBackReference 与@ManyToOne 实体和@JsonManagedReference 与包含实体类的@onetomany 一起使用。
@OneToMany(
mappedBy = "queue_group",fetch = FetchType.LAZY,
cascade = CascadeType.ALL
)
@JsonManagedReference
private Set<Queue> queues;
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name = "qid")
// @JsonIgnore
@JsonBackReference
private Queue_group queue_group;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
264584 次 |
| 最近记录: |