Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate session within inner class

I have an issue with getting session on anonymus inner class in hibernate with spring session factory. Here is the code:

public class DomainDaoImpl extends BasicDaoImpl<Domain> implements Iterable<Collection<Domain>> {

...

@Override
public Iterator<Collection<Domain>> iterator() {
    return (new Iterator<Collection<Domain>>() {
        private int counter = 0;
        public static final int LIMIT = 100;

        ...

        @Override
        @Transactional(readOnly = true)
        public Collection<Domain> next() {
            final Criteria criteria = getCurrentSession().createCriteria(Domain.class);
            final LinkedHashSet<Domain> result = new LinkedHashSet<Domain>();

            List resultList = null;
            while (!(resultList = criteria.list()).isEmpty()) {
                criteria.setFirstResult((counter++ * LIMIT) + 1);
                criteria.setMaxResults(LIMIT);
                result.addAll(resultList);
            }
            return result;
        }

        ...
    });

The issue is org.hibernate.HibernateException: No Session found for current thread this happens usually when DAO method is not within transaction. So how to make it work with an inner class?

like image 688
eugen-fried Avatar asked Oct 18 '12 12:10

eugen-fried


2 Answers

i think defining @Transactional(readOnly = true) at inner class level, spring will not be able to detect and apply transaction aspect over it. so it will not work for sure.

but i think if you write something like below might work not 100% sure (i doubt once you invoke iterator method transaction is closed)

@Override
@Transactional(readOnly = true)
public Iterator<Collection<Domain>> iterator() {
...
}

another option can be let caller be responsible for transaction or write wrapper method over iterator() like getAllDomain() and apply transaction to that method.

Solution which worked (mentioned in comments)

may be you can do some patch in getCurrentSession() like getCurrentSession() from sessionFactory if not available then use openSession(), ofcourse you have to close it manually if new session opened.

like image 101
Jigar Parekh Avatar answered Nov 12 '22 11:11

Jigar Parekh


You can configure load-time aspect weaving

Here is the basic example how to do it

Spring context

<?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:context="http://www.springframework.org/schema/context"
   xmlns:tx="http://www.springframework.org/schema/tx"
   xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">

    <context:component-scan base-package="org.foo.bar" />
    <context:annotation-config />
    <context:load-time-weaver />

    <tx:annotation-driven mode="aspectj" proxy-target-class="true"/>

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:schema.sql"/>
    </jdbc:embedded-database>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="annotatedClasses">
            <list>
                <value>org.foo.bar.MyEntity</value>
            </list>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

</beans>

Entity class

package org.foo.bar;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "ENTITY")
public class MyEntity {

    @Id
    private long id;
    private String name;

    public long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("MyEntity");
        sb.append("{id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

Dao class

package org.foo.bar;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Iterator;
import java.util.NoSuchElementException;

@Component
public class MyEntityDao implements Iterable<MyEntity> {

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public Iterator<MyEntity> iterator() {
        return new Iterator<MyEntity>() {
            private int num = 0;
            private MyEntity item;

            @Override
            @Transactional(readOnly = true)
            public boolean hasNext() {
                item = getEntity();
                return item != null;
            }

            @Override
            @Transactional(readOnly = true)
            public MyEntity next() {
                try {
                    if(item == null) {
                        item = getEntity();
                        if(item == null) {
                            throw new NoSuchElementException();
                        }
                    }
                    return item;
                } finally {
                    item = null;
                }
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }

            private MyEntity getEntity() {
                final Criteria criteria = getCurrentSession().createCriteria(MyEntity.class);
                criteria.setFirstResult(num++);
                criteria.setMaxResults(1);
                return (MyEntity) criteria.uniqueResult();
            }
        };
    }

    public Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }
}

SQL

drop table ENTITY if exists

create table ENTITY (id bigint generated by default as identity (start with 1),  name varchar(255),  primary key (id))

insert into ENTITY (name) values ('Entity1')
insert into ENTITY (name) values ('Entity2')
insert into ENTITY (name) values ('Entity3')

Unit test

package org.foo.bar;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.assertEquals;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath:applicationContext.xml"
})
public class MyEntityDaoTest {

    @Autowired
    private MyEntityDao dao;

    @Test
    public void testDao() throws Exception {
        int count = 0;
        for(MyEntity a : dao) {
            count++;
        }
        assertEquals(3, count);
    }

}

Here is my pom.xml http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0

    <groupId>org.foo.bar</groupId>
    <artifactId>spring-aspectj-hibernate</artifactId>
    <version>1.0</version>

    <properties>
        <spring.version>3.1.1.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.12</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.1.1.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.10</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

After all you have to run JVM with the following argument -javaagent:<PATH-TO>/spring-instrument-{vertion}.jar. To prevent adding -javaagent argument you can also configure aspectj compile-time weaving.

like image 1
szhem Avatar answered Nov 12 '22 12:11

szhem