Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson confused with bidirectional one-to-many relationship : failed to lazily initialize collection

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 :

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.

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.

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 :

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

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

    private static final long serialVersionUID = 1L;

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

}

Web XML i have this filter :

<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

like image 468
Ahmed Avatar asked May 26 '15 18:05

Ahmed


1 Answers

I solve that by adding this annotation @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;
    }
like image 186
Ahmed Avatar answered Nov 15 '22 08:11

Ahmed