Arnold Galovics
Arnold Galovics

Reputation: 3416

Spring 4 + Hibernate 4 transaction management error

I'm kinda new with the Spring's transaction management. I think I'm missing some configuration but I can't deal with it.

The error is that I'm getting failed to lazily initialize a collection of role exception.

I'm using Spring Data for my DAO. Also, I know about setting the fetchType to Eager but this is what I'm trying to avoid.

DAO:

public interface CourseDao extends CrudRepository<CourseEntity, Long> {
    CourseEntity findByName(String name);
}

Service:

@Service
public class CourseMaterialSearchService {
    private final CourseDao courseDao;
    private final CourseMaterialEntityTransformer courseMaterialEntityTransformer;

    @Autowired
    public CourseMaterialSearchService(CourseDao courseDao, CourseMaterialEntityTransformer courseMaterialEntityTransformer) {
        super();
        this.courseDao = courseDao;
        this.courseMaterialEntityTransformer = courseMaterialEntityTransformer;
    }

    @Transactional
    public List<CourseMaterial> findMaterialsFor(final Long courseId) {
        final CourseEntity entity = courseDao.findOne(courseId);
        final List<CourseMaterialEntity> materials = entity.getCourseMaterialEntityList();
        return courseMaterialEntityTransformer.transformEntities(materials);
    }
}

And my application-context.xml is (of course this is just the relevant part):

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="example" />
    <property name="dataSource" ref="exampleDataSource" />
    <property name="packagesToScan" value="com.example.example.**.repository" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="showSql" value="true" />
            <property name="generateDdl" value="true" />
        </bean>
    </property>
</bean>
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

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

When I call the findMaterialsFor method in CourseMaterialSearchService, the exception occurs. How should I solve this?

Any suggestions will be appreciated.

Thank you guys.

Stacktrace:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.example.course.repository.domain.CourseEntity.courseMaterialEntityList, could not initialize proxy - no Session
org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:575)
org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:214)
org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:554)
org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:142)
org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:294)
com.example.example.course.service.transform.CourseMaterialEntityTransformer.transformEntities(CourseMaterialEntityTransformer.java:15)
com.example.example.course.service.CourseMaterialSearchService.findMaterialsFor(CourseMaterialSearchService.java:29)

CourseMaterialEntityTransformer:

public class CourseMaterialEntityTransformer {
        public List<CourseMaterial> transformEntities(final Iterable<CourseMaterialEntity> entities) {
            final List<CourseMaterial> result = new ArrayList<>();
            for (final CourseMaterialEntity entity : entities) {
                result.add(transformEntity(entity));
            }
            return result;
        }

    public CourseMaterial transformEntity(final CourseMaterialEntity entity) {
        final CourseMaterial result = new CourseMaterial();
        result.setId(entity.getId());
        result.setName(entity.getName());
        result.setCourseName(entity.getCourseEntity().getName());
        result.setCurrentFileName(entity.getCurrentFileName());
        result.setOriginalFileName(entity.getOriginalFileName());
        result.setCategory(entity.getMaterialCategoryEntity().getName());
        result.setUploader(entity.getUserEntity().getName());
        return result;
    }
}

com.example.example.course.service.transform.CourseMaterialEntityTransformer.transformEntities(CourseMaterialEntityTransformer.java:15):

for (final CourseMaterialEntity entity : entities) {

CourseEntity:

@Entity(name = "courses")
public class CourseEntity {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "courseEntity")
    private List<CourseMaterialEntity> courseMaterialEntityList;

    public Long getId() {
        return id;
    }

    public void setId(final Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(final String name) {
        this.name = name;
    }

    public List<CourseMaterialEntity> getCourseMaterialEntityList() {
        return courseMaterialEntityList;
    }

    public void setCourseMaterialEntityList(final List<CourseMaterialEntity> courseMaterialEntityList) {
        this.courseMaterialEntityList = courseMaterialEntityList;
    }

}

Upvotes: 0

Views: 595

Answers (4)

M. Deinum
M. Deinum

Reputation: 124526

Spring applies transactions using AOP. AOP on beans is only applied to beans in the same application context.

You have a <tx:annotation-driven /> in your application-context.xml, which is loaded by the ContextLoaderListener. This file contains a <context:component-scan /> which detects the @Service.

However if you also have the same <context:component-scan /> in the file loaded by the DispatcherServlet, or at least if the same @Service is detected it results in another instance of this service which will not have AOP applied.

As a general rule-of-thumb you want to load everything BUT @Controllers in your ContextLoaderListener and only web related things (view resolvers, @Controllers) in your DispatcherServlet.

See context depended scan-component filter.

Upvotes: 1

Alan Barrows
Alan Barrows

Reputation: 609

You need to change @OneToMany(mappedBy = "courseEntity") to @OneToMany(mappedBy = "courseEntity", fetch = FetchType.EAGER, cascade = CascadeType.ALL)

The default fetch type is lazy so when you try and access the collection it hasn't been loaded from the database.

Upvotes: 0

Ye Win
Ye Win

Reputation: 2098

By default Collections are lazy-loaded . You can replace below your code :

@OneToMany(mappedBy = "courseEntity")
private List<CourseMaterialEntity> courseMaterialEntityList;

With :

@OneToMany(fetch = FetchType.EAGER, mappedBy = "courseEntity", cascade = CascadeType.ALL)
private List<CourseMaterialEntity> courseMaterialEntityList;

Upvotes: 0

Tobb
Tobb

Reputation: 12205

You are trying to access a lazy-loaded attribute outside of a transaction. The object then has no connection with the EntityManager (database), since it's outside of a transaction, and thus cannot go fetch the lazy-loaded attribute.

Upvotes: 0

Related Questions