【问题标题】:Jackson confused with bidirectional one-to-many relationship : failed to lazily initialize collection杰克逊对双向一对多关系感到困惑:未能懒惰地初始化集合
【发布时间】:2015-08-08 13:15:23
【问题描述】:

后端 Spring MVC 和 Spring-data,spring-security 。前端 Angularjs。我使用的是 spring 3.1;Jackson 1.8; JPa 2.1 ands 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"])

浏览器出现以下错误:

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

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

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

模型类:

@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;
    }

值对象设计模式代码

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}

存储库:

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);
}

服务类:

@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
@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 代码:

$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;
            }
    }

在 spring 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>

HibernateAwareObjectMapper 类的代码: 公共类 HibernateAwareObjectMapper 扩展 ObjectMapper {

    private static final long serialVersionUID = 1L;

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

}

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>

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

<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>  

有没有办法通过 Open Session 加载集合? 提前感谢您重播

【问题讨论】:

    标签: angularjs jackson lazy-loading spring-data-jpa spring-data-rest


    【解决方案1】:

    一种可能性是添加 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>
    

    【讨论】:

    • 感谢您的重播,我已经通过保留两个过滤器尝试了这个,但我得到了同样的错误,但是当我删除过滤器时,我可以访问我的登录页面,因为我使用弹簧安全性我有另一个错误;如下所示:java.lang.NullPointerException at interceptor.LoginInterceptor.preHandle(LoginInterceptor.java:27) at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(
    【解决方案2】:

    我通过添加这个注解@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;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-01-24
      • 2017-06-06
      • 1970-01-01
      • 1970-01-01
      • 2014-03-24
      • 2013-07-15
      • 1970-01-01
      相关资源
      最近更新 更多