java中lazy in_java – Jackson在序列化时触发JPA Lazy Fetchin...

我们有一个后端组件,它通过JPA将数据库(PostgreSQL)数据暴露给RESTful API.

问题是,当发送JPA实体作为REST响应时,我可以看到Jackson触发所有Lazy JPA关系.

代码示例(简化):

import org.springframework.hateoas.ResourceSupport;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;

import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import org.springframework.transaction.annotation.Transactional;

import javax.persistence.CascadeType;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.OneToMany;

import java.io.Serializable;

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

@Entity

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")//for resolving this bidirectional relationship, otherwise StackOverFlow due to infinite recursion

public class Parent extends ResourceSupport implements Serializable {

private static final long serialVersionUID = 1L;

@Id

@GeneratedValue

private Long id;

//we actually use Set and override hashcode&equals

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)

private List children = new ArrayList<>();

@Transactional

public void addChild(Child child) {

child.setParent(this);

children.add(child);

}

@Transactional

public void removeChild(Child child) {

child.setParent(null);

children.remove(child);

}

public Long getId() {

return id;

}

@Transactional

public List getReadOnlyChildren() {

return Collections.unmodifiableList(children);

}

}

import com.fasterxml.jackson.annotation.JsonIdentityInfo;

import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.JoinColumn;

import javax.persistence.ManyToOne;

import java.io.Serializable;

@Entity

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")

public class Child implements Serializable {

private static final long serialVersionUID = 1L;

@Id

@GeneratedValue

private Long id;

@ManyToOne

@JoinColumn(name = "id")

private Parent parent;

public Long getId() {

return id;

}

public Parent getParent() {

return parent;

}

/**

* Only for usage in {@link Parent}

*/

void setParent(final Parent parent) {

this.parent = parent;

}

}

import org.springframework.data.repository.CrudRepository;

public interface ParentRepository extends CrudRepository {}

import com.avaya.adw.db.repo.ParentRepository;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.hateoas.Link;

import org.springframework.http.HttpHeaders;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.CrossOrigin;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;

@RestController

@RequestMapping("/api/v1.0/parents")

public class ParentController {

private final String hostPath;

private final ParentRepository parentRepository;

public ParentController(@Value("${app.hostPath}") final String hostPath,

final ParentRepository parentRepository) {

// in application.properties: app.hostPath=/api/v1.0/

this.hostPath = hostPath;

this.parentRepository = parentRepository;

}

@CrossOrigin(origins = "*")

@GetMapping("/{id}")

public ResponseEntity> getParent(@PathVariable(value = "id") long id) {

final Parent parent = parentRepository.findOne(id);

if (parent == null) {

return new ResponseEntity<>(new HttpHeaders(), HttpStatus.NOT_FOUND);

}

Link selfLink = linkTo(Parent.class)

.slash(hostPath + "parents")

.slash(parent.getId()).withRel("self");

Link updateLink = linkTo(Parent.class)

.slash(hostPath + "parents")

.slash(parent.getId()).withRel("update");

Link deleteLink = linkTo(Parent.class)

.slash(hostPath + "parents")

.slash(parent.getId()).withRel("delete");

Link syncLink = linkTo(Parent.class)

.slash(hostPath + "parents")

.slash(parent.getId())

.slash("sync").withRel("sync");

parent.add(selfLink);

parent.add(updateLink);

parent.add(deleteLink);

parent.add(syncLink);

return new ResponseEntity<>(adDataSource, new HttpHeaders(), HttpStatus.OK);

}

}

所以,如果我发送GET … / api / v1.0 / parents / 1,响应如下:

{

"id": 1,

"children": [

{

"id": 1,

"parent": 1

},

{

"id": 2,

"parent": 1

},

{

"id": 3,

"parent": 1

}

],

"links": [

{

"rel": "self",

"href": "http://.../api/v1.0/parents/1"

},

{

"rel": "update",

"href": "http://.../api/v1.0/parents/1"

},

{

"rel": "delete",

"href": "http://.../api/v1.0/parents/1"

},

{

"rel": "sync",

"href": "http://.../api/v1.0/parents/1/sync"

}

]

}

但我希望它不包含子节点或包含它作为空数组或null – 不从数据库中获取实际值.

该组件具有以下值得注意的maven依赖项:

- Spring Boot Starter 1.5.7.RELEASE

- Spring Boot Starter Web 1.5.7.RELEASE (version from parent)

- Spring HATEOAS 0.23.0.RELEASE

- Jackson Databind 2.8.8 (it's 2.8.1 in web starter, I don't know why we overrode that)

- Spring Boot Started Data JPA 1.5.7.RELEASE (version from parent) -- hibernate-core 5.0.12.Final

到目前为止尝试过

调试显示在parentRepository.findOne(id)的Parent上有一个select,在序列化期间在Parent.children上有另一个select.

起初,我尝试将@JsonIgnore应用于延迟集合,但即使它实际上包含某些内容(已经被提取),它也会忽略该集合.

build Jackson module (jar) to support JSON serialization and

deserialization of Hibernate (07001) specific datatypes

and properties; especially lazy-loading aspects.

这个想法是将Hibernate5Module(如果使用了hibernate的第5版)注册到ObjectMapper,并且应该这样做,因为默认情况下模块的设置FORCE_LAZY_LOADING设置为false.

所以,我包含了这个依赖项jackson-datatype-hibernate5,版本2.8.10(来自父级).用谷歌搜索到enable it in Spring Boot(我也找到了其他来源,但他们大多指的是这个).

1.直接添加模块(特定于Spring Boot):

import com.fasterxml.jackson.databind.Module;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class HibernateConfiguration {

@Bean

public Module disableForceLazyFetching() {

return new Hibernate5Module();

}

}

调试显示,Spring返回Parent时调用的ObjectMapper包含此模块,并且强制延迟设置设置为false,正如预期的那样.但它仍然带走了孩子.

进一步调试显示:com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields遍历属性(com.fasterxml.jackson.databind.ser.BeanPropertyWriter)并调用其方法serializeAsField,其中第一行是:final Object value =(_ accesscessMethod == null)? _field.get(bean):_ accessorMethod.invoke(bean);这是触发延迟加载.我无法找到代码实际上关心hibernate模块的任何地方.

upd还尝试启用SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS,它应该包含惰性属性的实际id,而不是null(这是默认值).

@Bean

public Module disableForceLazyFetching() {

Hibernate5Module module = new Hibernate5Module();

module.enable(Hibernate5Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);

return module;

}

调试显示该选项已启用但仍然没有效果.

2.指示Spring MVC添加模块:

import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;

import org.springframework.context.annotation.Configuration;

import org.springframework.http.converter.HttpMessageConverter;

import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

@Configuration

@EnableWebMvc

public class HibernateConfiguration extends WebMvcConfigurerAdapter {

@Override

public void configureMessageConverters(List> converters) {

Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()

.modulesToInstall(new Hibernate5Module());

converters.add(new MappingJackson2HttpMessageConverter(builder.build()));

}

}

这也成功地将模块添加到正在调用的ObjectMapper中,但在我的情况下它仍然没有效果.

3.用一个新的完全替换ObjectMapper:

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Primary;

@Configuration

public class HibernateConfiguration {

@Primary

@Bean(name = "objectMapper")

public ObjectMapper hibernateAwareObjectMapper(){

ObjectMapper mapper = new ObjectMapper();

mapper.registerModule(new Hibernate5Module());

return mapper;

}

}

再次,我可以看到添加了模块,但这对我没有任何影响.

还有其他方法可以添加此模块,但我没有失败,因为添加了模块.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值