简体   繁体   中英

LazyLoadInit exception with Spring + Spring JPA data + hibernate

I can't figure this out. Am getting "Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.sandy.domain.Location.items, no session or session was closed" I understand that session is closed but tx:annotation-driver @Transactional should ensure an open session. It works fine with EAGER fetching. Oh and yea - this is by apress pro spring 3 examples.

But maybe I don't get the concept here - I mean while I am debugging "getFirst()" in SomeService I can see all the items in the collection but once return hits - LazyInit exception is thrown...

package org.sandy.main;

    import org.sandy.domain.Item;
    import org.sandy.domain.Location;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.support.GenericXmlApplicationContext;
    import org.springframework.stereotype.Service;

@Service(value = "main")
public class EntryPoint {

    @Autowired
    private SomeService ss;

    public static void main(String args[]) {
        GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
        ctx.load("classpath:*-beans.xml");
        ctx.refresh();

        EntryPoint entryPoint = (EntryPoint) ctx.getBean("main");

        Item item = new Item();
        Location location = new Location();

        item.setLocation(location);
        location.getItems().add(item);

        entryPoint.getSs().save(location);

        System.out.println(entryPoint.getSs().findFirst());

        ctx.registerShutdownHook();
    }

    public SomeService getSs() {
        return ss;
    }

    public void setSs(SomeService ss) {
        this.ss = ss;
    }
}

and the service

package org.sandy.main;

import org.sandy.domain.Item;
import org.sandy.domain.Location;
import org.sandy.repo.ItemRepo;
import org.sandy.repo.LocationRepo;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service(value = "ss")
@Repository
@Transactional
public class SomeService {
    @Autowired
    private ItemRepo itemRepo;
    @Autowired
    private LocationRepo locationRepo;

    @Transactional
    public void save(Location location) {
        locationRepo.save(location);
    }

    @Transactional(readOnly = true)
    public List<Item> findFirst() {
        System.out.println("ONE");
        Iterable<Location> it = locationRepo.findAll();
        System.out.println("TWO");
        Location l = it.iterator().next();
        System.out.println("THREE");
        List<Item> r = l.getItems();
        return r;
    }

    @Transactional
    public Iterable finAll() {
        return locationRepo.findAll();
    }

    public void setItemRepo(ItemRepo itemRepo) {
        this.itemRepo = itemRepo;
    }

    public void setLocationRepo(LocationRepo locationRepo) {
        this.locationRepo = locationRepo;
    }
}

and location repo

package org.sandy.repo;

import org.sandy.domain.Location;
import org.springframework.data.repository.CrudRepository;

public interface LocationRepo extends CrudRepository<Location, Long> {
}

Entities:

@Entity
@Table(name = "Locations")
public class Location extends Entry {

    private List<Item> items = new ArrayList<Item>();

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "location")
    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }
}

package org.sandy.domain;

import javax.persistence.*;

@Entity
@Table(name = "Items")
public class Item extends Entry {
    private Location location;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn()
    public Location getLocation() {
        return location;
    }

    public void setLocation(Location location) {
        this.location = location;
    }
}

And the all mighty xml bean config:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <jdbc:embedded-database id="dataSource" type="H2" />

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="emf"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="packagesToScan" value="org.sandy.domain" />
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">
                    org.hibernate.dialect.H2Dialect
                </prop>
                <prop key="hibernate.max_fetch_depth">3</prop>
                <prop key="hibernate.jdbc.fetch_size">50</prop>
                <prop key="hibernate.jdbc.batch_size">10</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
                <prop key="hibernate.enable_lazy_load_no_trans">true</prop>
            </props>
        </property>
    </bean>

    <context:annotation-config/>

    <jpa:repositories base-package="org.sandy.repo" entity-manager-factory-ref="emf" transaction-manager-ref="transactionManager" />

    <context:component-scan base-package="org.sandy" />
</beans>

This piece of code

System.out.println(entryPoint.getSs().findFirst());

is equivalent to

/* 1 */ SomeService ss = entryPoint.getSs();
/* 2 */ List<Item> items = ss.findFirst(); // Session opens and closes for @Transactional 
/* 3 */ String toPrint = items.toString(); // no more Session
/* 4 */ System.out.println(toPrint);

So you can see that the Session boundary is only wrapping the findFirst() call. If you're loading your entities lazily, then on line 3, the elements in items are not initialized. When you try to call toString() inside List#toString() which calls toString() on each element, you will get your LazyInitializationException .

You should fully initialize your entities before you use them. That has to be done within Session boundaries.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM