杰克逊与双向一对多关系混淆:未能懒惰地初始化收藏

时间:2021-07-08 18:01:28

Backend Spring MVC and Spring-data,spring-security . Front end Angularjs.I'm using spring 3.1 ;Jackson 1.8 ; JPa 2.1 ands mysql.The basic problem is the same as asked multiple time. I have a simple program with two POJOs sites and typeSite- where a typeSite can have multiple Sites. But I'm getting the following error :

后端Spring MVC和Spring-data,spring-security。前端Angularjs.I使用弹簧3.1;杰克逊1.8; JPa 2.1和mysql。基本问题与多次询问相同。我有一个简单的程序,有两个POJO站点和typeSite-其中一个typeSite可以有多个站点。但是我收到以下错误:

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: TypeSites.sitees, could not initialize proxy - no Session (through reference chain: vo.TypeSitesListVO["typesites"]->java.util.UnmodifiableRandomAccessList[0]-model.TypeSites["sitees"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: model.TypeSites.sitees, could not initialize proxy - no Session (through reference chain:vo.TypeSitesListVO["typesites"]->java.util.UnmodifiableRandomAccessList[0]->model.TypeSites["sitees"])   
 Caused by: com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: model.TypeSites.sitees, could not initialize proxy - no Session (through reference chain: vo.TypeSitesListVO["typesites"]->java.util.UnmodifiableRandomAccessList[0]->model.TypeSites["sitees"])

And this following error in browser :

并在浏览器中出现以下错误:

Failed to load resource: the server responded with a status of 500 (Internal Server Error)

So to understand better this error let us see how the JPA/Hibernate handles the relationship. Every time we do a query in the database the JPA will bring to all information of that class. The exception to this rule is when we talk about list (collection). Notice in the above code, that the database query will return a Sitesobject. When i access the site collection, the container will notice that the site collection is a lazy attribute and it will “ask” the JPA to load this collection from the database.

因此,为了更好地理解这个错误,让我们看看JPA / Hibernate如何处理这种关系。每次我们在数据库中进行查询时,JPA都会带来该类的所有信息。这个规则的例外是当我们谈论列表(集合)时。请注意,在上面的代码中,数据库查询将返回Sitesobject。当我访问网站集时,容器会注意到网站集是一个惰性属性,它将“询问”JPA从数据库加载此集合。

In the moment of the query (that will bring the site collection) execution, an exception will happen. When the JPA/Hibernate tries to access the database to get this lazy information, the JPA will notice that there is no opened collection. That is why the exception happens, the lack of an opened database connection.

在查询(将带来网站集)执行的那一刻,将发生异常。当JPA / Hibernate尝试访问数据库以获取此惰性信息时,JPA会注意到没有打开的集合。这就是异常发生的原因,缺少打开的数据库连接。

model class :

型号类:

@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Sites implements java.io.Serializable {

    private static final long   serialVersionUID = 1L;
    private int                 id;
    private TypeSites           siteesTypeSite;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public int getId() {
        return id;
    }
    //@ManyToOne(fetch = FetchType.LAZY) 
    //@JoinColumn(name ="idTypeSite")  
    //@JsonIgnore
    @JsonBackReference("site-typeSite")
    @ManyToOne
    @JoinColumn(name = "idTypeSite", foreignKey = @ForeignKey(name = "fk_site_typeSite"))
    public TypeSites getSiteesTypeSite() {
        return siteesTypeSite;
    }
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="idTypeSite")
public class TypeSites implements java.io.Serializable {
    private int                     idTypeSite;
    private Set<Sites>              sitees= new  HashSet<Sites>(0);

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public int getIdTypeSite() {
        return idTypeSite;
    }
    //@JsonSerialize(using = CustomListSerializer.class)
    //@JsonView(Views.Internal.class)
    //@JsonIgnore
    @JsonManagedReference("site-typeSite")
    @OneToMany(mappedBy = "siteesTypeSite", cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    public Set<Sites> getSitees() {
        return sitees;
    }

code of value object design pattern

价值代码对象设计模式

public class TypeSitesListVO {
    private int         pagesCount;
    private long        totalTypeSite;
    private String      actionMessage;
    private String      searchMessage;
    private List<TypeSites> typesites;
    //setters/gtters}
public class SitesListVO {
    private int pagesCount;
    private long totalSites;
    private String actionMessage;
    private String searchMessage;
    private List<Sites> sites = new ArrayList<Sites>();
    //setters/gtters}

Repository :

public interface SitesRepository extends PagingAndSortingRepository<Sites, Integer> {
    //@Query("SELECT s FROM Sites s TypeSites ts JOIN FETCH s.siteesTypeSite WHERE s.id =ts.idTypeSite ")
    //@EntityGraph(value = "sites.type", type = EntityGraphType.LOAD)
    Page<Sites> findBycodeGSMLike(Pageable pageable, String codeGSM);
    //Page<Sites> findBycodeGSMLike(Pageable pageable, List<String> codeGSM);
}

Services class :

服务类:

@Service
@Transactional
public class SitesService {
    @Autowired
    private SitesRepository siteRepository;
    @Transactional(readOnly = true)
    public SitesListVO findAll(int page, int maxResults) {
        Page<Sites> result = executeQueryFindAll(page, maxResults);
                if(shouldExecuteSameQueryInLastPage(page, result)){
                int lastPage = result.getTotalPages() - 1;
    //            for (Sites e: result){
    //                Hibernate.initialize(e.getSiteesTypeSite());
    //            }
                result = executeQueryFindAll(lastPage, maxResults);
            }
            return buildResult(result);
        }
        public void save(Sites site) {
            siteRepository.save(site);
        }
        @Transactional(readOnly = true)
        public SitesListVO findBycodeGSMLike(int page, int maxResults, String codeGSM) {
            Page<Sites> result = executeQueryFindByName(page, maxResults, codeGSM);

            if(shouldExecuteSameQueryInLastPage(page, result)){
                int lastPage = result.getTotalPages() - 1;
    //            for (Sites e: result){
    //                Hibernate.initialize(e.getSiteesTypeSite());
    //            }
                result = executeQueryFindByName(lastPage, maxResults, codeGSM);
            }
            return buildResult(result);
        }
        private boolean shouldExecuteSameQueryInLastPage(int page, Page<Sites> result) {
            return isUserAfterOrOnLastPage(page, result) && hasDataInDataBase(result);
        }
        private Page<Sites> executeQueryFindAll(int page, int maxResults) {
            final PageRequest pageRequest = new PageRequest(page, maxResults, sortBycodeGSMASC());
    //        Page<Sites> SitesList = siteRepository.findAll(pageRequest); 
    //        for (Sites e: SitesList){
    //            Hibernate.initialize(e.getSiteesTypeSite());
    //        }
    //        return SitesList; 
            return siteRepository.findAll(pageRequest);
        }

        private Sort sortBycodeGSMASC() {
            return new Sort(Sort.Direction.ASC, "codeGSM");
        }

        private SitesListVO buildResult(Page<Sites> result) {

            return new SitesListVO(result.getTotalPages(), result.getTotalElements(), result.getContent());
        }

        private Page<Sites> executeQueryFindByName(int page, int maxResults, String codeGSM) {
            final PageRequest pageRequest = new PageRequest(page, maxResults, sortBycodeGSMASC());

    //      Page<Sites> SitesList = siteRepository.findBycodeGSMLike(pageRequest, codeGSM); 
    //      for (Sites e: SitesList){
    //          Hibernate.initialize(e.getSiteesTypeSite());
    //      }
    //      return SitesList; 
          return siteRepository.findBycodeGSMLike(pageRequest, codeGSM); 
        }
        private boolean isUserAfterOrOnLastPage(int page, Page<Sites> result) {
            return page >= result.getTotalPages() - 1;
        }

        private boolean hasDataInDataBase(Page<Sites> result) {
            return result.getTotalElements() > 0;
        }
    }

controller class :

控制器类:

@Controller
@RequestMapping(value = "/protected/sites")
public class SitesController {
  private static final String DEFAULT_PAGE_DISPLAYED_TO_USER = "0";
   @Autowired
    private SitesService siteService;
    @Autowired
    private MessageSource messageSource;

    @Value("5")
    private int maxResults;

    @RequestMapping(method = RequestMethod.GET)
    public ModelAndView welcome() {
        return new ModelAndView("sitesList");
    }
    @RequestMapping(method = RequestMethod.GET, produces = "application/json")
    public ResponseEntity<?> listAll(@RequestParam int page, Locale locale) {

        return createListAllResponse(page, locale);
    }

    @RequestMapping(method = RequestMethod.POST, produces = "application/json")
    public ResponseEntity<?> create(@ModelAttribute("site") Sites site,
                                    @RequestParam(required = false) String searchFor,
                                    @RequestParam(required = false, 
                                    defaultValue = DEFAULT_PAGE_DISPLAYED_TO_USER) int page,
                                    Locale locale) {
        siteService.save(site);

        if (isSearchActivated(searchFor)) {
            return search(searchFor, page, locale, "message.create.success");
        }

        return createListAllResponse(page, locale, "message.create.success");
    }


    private SitesListVO listAll(int page) {
        return siteService.findAll(page, maxResults);
    }
    private ResponseEntity<SitesListVO> returnListToUser(SitesListVO siteList) {
        return new ResponseEntity<SitesListVO>(siteList, HttpStatus.OK);
    }
    private ResponseEntity<?> createListAllResponse(int page, Locale locale) {
        SitesListVO siteListVO = listAll(page);
        return createListAllResponse(page, locale, null);
    }

    private ResponseEntity<?> createListAllResponse(int page, Locale locale, String messageKey) {
        SitesListVO siteListVO = listAll(page);
        addActionMessageToVO(siteListVO, locale, messageKey, null);
        return returnListToUser(siteListVO);
    }
    private SitesListVO addActionMessageToVO(SitesListVO siteListVO, Locale locale, String actionMessageKey, Object[] args) {
        if (StringUtils.isEmpty(actionMessageKey)) {
            return siteListVO;
        }
        siteListVO.setActionMessage(messageSource.getMessage(actionMessageKey, args, null, locale));
        return siteListVO;
    }
    private SitesListVO addSearchMessageToVO(SitesListVO siteListVO, Locale locale, String actionMessageKey, Object[] args) {
        if (StringUtils.isEmpty(actionMessageKey)) {
            return siteListVO;
        }
        siteListVO.setSearchMessage(messageSource.getMessage(actionMessageKey, args, null, locale));
        return siteListVO;
    }
    private boolean isSearchActivated(String searchFor) {
        //return !CollectionUtils.isEmpty(searchFor);
        return !StringUtils.isEmpty(searchFor);
    }
}

AngularJs code :

AngularJs代码:

$scope.getContactList = function () {
        var url = $scope.url;
        $scope.lastAction = 'list';

        $scope.startDialogAjaxRequest();

        var config = {params: {page: $scope.pageToGet}};

        $http.get(url, config)

            .success(function (data) {
//              console.log(data);
                console.debug(data);
                $scope.finishAjaxCallOnSuccess(data, null, false);
            })
            .error(function () {
                $scope.state = 'error';
                $scope.displayCreateContactButton = false;
            });
    }
$scope.populateTable = function (data) {
            if (data.pagesCount > 0) {
                $scope.state = 'list';

                $scope.page = {source: data.sites, currentPage: $scope.pageToGet, pagesCount: data.pagesCount, totalContacts : data.totalContacts};

                if($scope.page.pagesCount <= $scope.page.currentPage){
                    $scope.pageToGet = $scope.page.pagesCount - 1;
                    $scope.page.currentPage = $scope.page.pagesCount - 1;
                }

                $scope.displayCreateContactButton = true;
                $scope.displaySearchButton = true;
            } else {
                $scope.state = 'noresult';
                $scope.displayCreateContactButton = true;

                if(!$scope.searchFor){
                    $scope.displaySearchButton = false;
                }
            }
            if (data.actionMessage || data.searchMessage) {
                $scope.displayMessageToUser = $scope.lastAction != 'search';

                $scope.page.actionMessage = data.actionMessage;
                $scope.page.searchMessage = data.searchMessage;
            } else {
                $scope.displayMessageToUser = false;
            }
    }

In spring mvc XML i have :

在春天mvc XML我有:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>

            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean class="filter.HibernateAwareObjectMapper" />
                </property>
            </bean>
            <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
        </list>
    </property>
</bean>

code of class HibernateAwareObjectMapper : public class HibernateAwareObjectMapper extends ObjectMapper {

HibernateAwareObjectMapper类的代码:public class HibernateAwareObjectMapper extends ObjectMapper {

    private static final long serialVersionUID = 1L;

    public HibernateAwareObjectMapper() {
        registerModule(new Hibernate4Module());
    }

}

Web XML i have this filter :

Web XML我有这个过滤器:

<filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

; i'm using the following dependency :

;我正在使用以下依赖项:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.13</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.3.3</version>
</dependency>
<dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-annotations</artifactId>
     <version>2.3.0</version>
</dependency>
<dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-hibernate4</artifactId>
      <version>2.4.0</version>
</dependency>  

is there a way to Load collection by Open Session ? thank you in advance for you replay

有没有办法通过Open Session加载收集?谢谢你提前重播

2 个解决方案

#1


10  

I solve that by adding this annotation @LazyCollection(LazyCollectionOption.FALSE)

我通过添加这个注释来解决这个问题@LazyCollection(LazyCollectionOption.FALSE)

    //@JsonSerialize(using = CustomListSerializer.class)
    //@JsonView(Views.Internal.class)
    //@JsonIgnore
    @LazyCollection(LazyCollectionOption.FALSE)
    @OneToMany(mappedBy = "siteesTypeSite", cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    public Set<Sites> getSitees() {
        return sitees;
    }

#2


0  

One possibility is to add the OpenEntityManagerInViewFilter which keeps the session open in the view. Example:

一种可能性是添加OpenEntityManagerInViewFilter,使会话在视图中保持打开状态。例:

<filter>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

#1


10  

I solve that by adding this annotation @LazyCollection(LazyCollectionOption.FALSE)

我通过添加这个注释来解决这个问题@LazyCollection(LazyCollectionOption.FALSE)

    //@JsonSerialize(using = CustomListSerializer.class)
    //@JsonView(Views.Internal.class)
    //@JsonIgnore
    @LazyCollection(LazyCollectionOption.FALSE)
    @OneToMany(mappedBy = "siteesTypeSite", cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    public Set<Sites> getSitees() {
        return sitees;
    }

#2


0  

One possibility is to add the OpenEntityManagerInViewFilter which keeps the session open in the view. Example:

一种可能性是添加OpenEntityManagerInViewFilter,使会话在视图中保持打开状态。例:

<filter>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>