【问题标题】:Failed to lazily initialize a collection of role during conversion of object to json对象转json时懒惰初始化角色集合失败
【发布时间】:2018-05-31 17:53:18
【问题描述】:

我是 spring 新手,在从与其他表有关系的表中获取记录时,遇到了这个懒惰的初始化错误。 我在网上看了很多,但没有找到合适的方法。

表 1:

@SuppressWarnings("serial")
@Entity
public class Terminal extends BaseEntity {

@Column(length = 100, unique = true)
private String shortName;

@Column
private short number; // short stores up to 32767 value

@Column
private String description;     

@OneToMany
@JoinColumn(name = "terminal_id", referencedColumnName = "uuid")
@Cascade({ CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DELETE })
private Set<BusinessHour> businessHour;

 public String getShortName() {
    return shortName;
}

public void setShortName(String shortName) {
    this.shortName = shortName;
}

public short getNumber() {
    return number;
}

public void setNumber(short number) {
    this.number = number;
}

public String getDescription() {
    return description;
}

public void setDescription(String description) {
    this.description = description;
}
public Set<BusinessHour> getBusinessHour() {
    return businessHour;
}

public void setBusinessHour(Set<BusinessHour> businessHour) {
    this.businessHour = businessHour;
}

表 2:

@SuppressWarnings("serial")
@Entity
public class BusinessHour extends BaseEntity {

@Column
private DayOfWeek dayOfWeek;

@Column
private LocalTime startOfOperation;

@Column
private LocalTime endOfOperation;

public DayOfWeek getDayOfWeek() {
    return dayOfWeek;
}
}

服务代码:

@Service
public class TerminalServiceImpl implements TerminalService {

@Autowired
TerminalRepository terminalRepository;


    Iterable<Terminal> allTerminals = terminalRepository.findAll();
    List<Terminal> terminalList =  new ArrayList<Terminal>();
    for (Terminal terminal : allTerminals) {
        terminalList.add(terminal);
    }
    return terminalList;
}

终端存储库代码:

@Transactional
public interface TerminalRepository extends CrudRepository<Terminal, Long> {
}

调试时出错的代码:

private List<Terminal> updateTerminalList() {
    List<Terminal> allTerminals = terminalService.fetchAllTerminal();
    return allTerminals;
}

public void terminalWrapperRun() {
    try {
       Payload payload = createTerminalPayload(applicationId);
        String json3 = object2Json(payload);
        kafkaRESTUtils.sendServerPayload(json3);
    } catch (Exception e1) {
        e1.printStackTrace();
    }
}

 public String object2Json(Object dataArray) throws JsonProcessingException {
    return mapper.writeValueAsString(dataArray);
}

错误:

com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: terminal.model.Terminal.businessHour, could not initialize proxy - no Session (through reference chain: 

将获取对象转换为 json 时出现异常。我发现这是由于 fetch 类型延迟导致的代理对象返回(我想保持原样)。

【问题讨论】:

标签: java json hibernate spring-boot jackson


【解决方案1】:

我相信这个问题与您的 ORM 默认情况下延迟加载集合有关。

@OneToMany
@JoinColumn(name = "terminal_id", referencedColumnName = "uuid")
@Cascade({ CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DELETE })
private Set<BusinessHour> businessHour;

@OneToMany 注释有一个 fetch 属性,默认设置为 LAZY。

FetchType fetch() default LAZY;

OneToMany reference

这意味着它只会在访问数据时加载。在您的示例中,这将在您尝试创建 JSON 字符串时发生。但是,此时您已超出 ORM 会话的范围,因此它不知道如何加载数据。

因此,您有 2 个选项。

  1. 更改您的注释以立即加载数据(这意味着 BusinessHour Set 将与父对象同时加载

    @OneToMany(fetch = FetchType.EAGER)

  2. 在 ORM 会话中执行 JSON 生成(我只真正建议这样做,因为第一个选项会导致性能问题)。

【讨论】:

  • 我将如何在 ORM 会话中生成 json,您能提供帮助吗?
  • 简要看一下提供的代码,我认为实现这一点的最简单方法是在生成 JSON 的方法周围引入一个 Transactional 注释。这应该在所需的会话中执行代码。话虽如此,我认为这会给代码带来误导性的表示。也许在调用 JSON 生成之前,您应该引入一个单独的方法,该方法使用 Hibernate.initialize(terminal.getBusinessHour()) 初始化您的 businessHour Set
【解决方案2】:

如果我没记错的话,这是由于Entity 在使用时与EntityManager 分离而导致的错误(作为Proxy,它无法执行数据库查询来检索数据) .

你可以使用:

@OneToMany(fetch = FetchType.EAGER)
...
private Set<BusinessHour> businessHour;

【讨论】:

  • 我不想使用 fetchType 作为 EAGER 我们可以有其他方法吗
  • 为什么?几乎没有性能损失,您不必使事情变得更加复杂。恕我直言,这是一个更清洁的解决方案
【解决方案3】:

使用 FetchType=EAGER 意味着针对您的实体的任何查询都将加载整批带注释的实体。

恕我直言,如果您 100% 确定您的实体将仅用于您的特殊业务案例,这才是明智之举。

在所有其他情况下 - 例如将数据访问编程为库,或接受对实体的不同类型的查询,您应该使用实体图 (https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs002.htm) 或显式加载(Hibernate.initialize、join-fetch,请参阅例如https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/)。

如果您的用例只是转换,您有两个不错的选择:

  • 在事务方法中将您的实体转换为 JSON(如 PillHead 建议的那样)
  • 在事务中使用所有需要的实体(通过实体图或 Hibernate.initialize)显式加载您的实体,然后在您需要的地方转换为 JSON。

【讨论】:

    猜你喜欢
    • 2011-07-21
    • 2017-03-11
    • 1970-01-01
    • 1970-01-01
    • 2019-01-23
    • 2011-10-23
    相关资源
    最近更新 更多