2

To speed up iteration of all rows in a SQL table using Hibernate (since JPA doesn't support streaming), I followed the approach of this answer. While this works well, this answer tells us that we should retrieve the Session object using

Session session = entityManager.unwrap(Session.class);

instead of the way it was done in the answer:

Session session = (Session) manager.getDelegate();

Hower, with this change I suddenly get the following exception:

java.lang.IllegalStateException: No transactional EntityManager available

The entity manager is autowired into a field in a Spring component like this:

@Component
public class Updater {
    @Autowired
    private EntityManager entityManager;

    @Transactional
    public void update() {
        // ...
        Result loadedResult = loadAll()
        // ...
    }

    private Result loadAll() {
        Session session = (Session) manager.getDelegate();
        //Session session = entityManager.unwrap(Session.class);

        SessionFactory sessionFactory = session.getSessionFactory();
        StatelessSession statelessSession = sessionFactory.openStatelessSession();

        // ... @linked answer ...
    }
}

As the name suggests, loadAll only reads data and transforms it into some Result. update does write to the database.

Note that loadAll is only called through update. Also, annotating loadAll with @Transactional doesn't fix the problem.

I know there are several other questions regarding this error, stating @Transactional to be the answer. My question is what the difference between getDelegate and unwrap really is: Why does the one fail and the other not? (And why doesn't @Transactional fix the problem?)

I'm using H2 1.4.190 and Hibernate 4.3.11.Final (through Spring Boot 1.3.2.RELEASE).

EDIT Full minimal example (with package declarations and imports omitted). All classes are in package com.example:

Entity.java

@javax.persistence.Entity
public class Entity {

    @Id
    @GeneratedValue
    public int id;

    public int value;
}

EntityRepository.java

public interface EntityRepository extends JpaRepository<Entity, Integer> {
}

Config.java:

@Configuration
@ComponentScan
@EnableJpaRepositories
@EntityScan
@EnableTransactionManagement
public class Config {
}

Runner.java:

@SpringBootApplication
public class Runner implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(Runner.class);
        application.setWebEnvironment(false);
        application.run(args);
    }

    @Autowired
    private Updater updater;

    @Override
    public void run(String... args) throws Exception {
        updater.insert(1, 4, 2);
        updater.update();

        updater.printAll();
    }
}

Updater.java:

@Component
public class Updater {

    @Autowired
    private EntityRepository repository;

    @PersistenceContext //@Autowired
    private EntityManager entityManager;

    public void insert(int... values) {
        for (int value : values) {
            Entity entity = new Entity();
            entity.value = value;
            repository.save(entity);
        }
        repository.flush();
    }

    public void update() {
        // Call "transactioned" method through an intermediary method.
        // The code works if 'Runner' calls 'transactionedUpdate' directly.
        transactionedUpdate();
    }

    @Transactional
    public void transactionedUpdate() {
        int sum = loadAll();

        // Set all 'value's to 'sum'.
        List<Entity> entities = repository.findAll();
        for (Entity entity : entities) {
            entity.value = sum;
            repository.save(entity);
        }
        repository.flush();
    }

    public int loadAll() {
//        Session session = (Session) entityManager.getDelegate();
        Session session = entityManager.unwrap(Session.class);

        SessionFactory sessionFactory = session.getSessionFactory();
        StatelessSession statelessSession = sessionFactory.openStatelessSession();

        Query query = statelessSession.createQuery("FROM com.example.Entity e");
        query.setFetchSize(1000);
        query.setReadOnly(true);
        query.setLockMode("e", LockMode.NONE);
        ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);

        int sum = 0;
        while (results.next()) {
            Entity entity = (Entity) results.get(0);
            sum += entity.value;
        }

        results.close();
        statelessSession.close();

        return sum;
    }

    public void printAll() {
        List<Entity> entities = repository.findAll();
        for (Entity entity : entities) {
            System.out.println(entity.id + ": " + entity.value);
        }
    }
}

application.yml:

spring:
    jpa:
        open-in-view: false
        hibernate:
            ddl-auto: update
            naming-strategy: org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy
        database: H2
        show_sql: false
        properties:
            hibernate.cache.use_second_level_cache: true
            hibernate.cache.use_query_cache: false
    datasource:
        driver-class-name: org.h2.Driver
        url: jdbc:h2:file:~/data-test/db;DB_CLOSE_DELAY=-1
        name:
        username: test
        password:

Stack trace:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:809) ~[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:790) ~[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:777) ~[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    at com.example.Runner.main(Runner.java:16) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_60]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_60]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_60]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_60]
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) [idea_rt.jar:na]
Caused by: java.lang.IllegalStateException: No transactional EntityManager available
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:268) ~[spring-orm-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at com.sun.proxy.$Proxy49.unwrap(Unknown Source) ~[na:na]
    at com.example.Updater.loadAll(Updater.java:49) ~[classes/:na]
    at com.example.Updater.doUpdate(Updater.java:36) ~[classes/:na]
    at com.example.Updater.update(Updater.java:31) ~[classes/:na]
    at com.example.Updater$$FastClassBySpringCGLIB$$503dcdb8.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:651) ~[spring-aop-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at com.example.Updater$$EnhancerBySpringCGLIB$$f362c2c8.update(<generated>) ~[classes/:na]
    at com.example.Runner.run(Runner.java:25) [classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:806) ~[spring-boot-1.3.2.RELEASE.jar:1.3.2.RELEASE]
    ... 9 common frames omitted
Community
  • 1
  • 1
bisgardo
  • 4,130
  • 4
  • 29
  • 38
  • Don't use `@Autowired` use `@PersistenceContext` when injecting an `EntityManager`. – M. Deinum Mar 29 '16 at 08:31
  • While this does remove IntelliJ's "Could not autowire..." inspection error, it doesn't change the observed behavior. – bisgardo Mar 29 '16 at 12:09
  • Post the full stack trace. – M. Deinum Mar 29 '16 at 12:16
  • 1
    update() method is not annotated with @Transactional ? – Thierry Mar 29 '16 at 13:20
  • Your methods aren't `@Transactional` so no transaction. This is also clear from your stakctrace as there is no transactional proxy for the `Updater` – M. Deinum Mar 29 '16 at 13:21
  • Sorry, I forgot to include that in the example (which indeed works if `update` is `@Transactional`). I have updated the example to be more like the original code. The difference is that the "transactioned" method is called through an intermediary method. But why does that make a difference? – bisgardo Mar 29 '16 at 14:26

1 Answers1

0

Well, I'm not using Spring, but when I wanted to get the SessionFactory for performance reasons I simply declared it in a Stateless EJB:

@Stateless
public class OpinionViewCache {

    @PersistenceUnit(unitName="opee") SessionFactory sessionFactory;
    private StatelessSession statelessSession;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

And to get session I used a simple call:

statelessSession = sessionFactory.openStatelessSession();

Closed it when I was done:

statelessSession.close();

Otherwise the code I used for testing from Java SE was pretty straight forward:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("opjpa");
EntityManager em = emf.createEntityManager();
EntityManagerImpl emImpl = (EntityManagerImpl)em;
HibernateEntityManagerFactory factory = emImpl.getFactory();
SessionFactory sessionFactory = factory.getSessionFactory();
K.Nicholas
  • 10,956
  • 4
  • 46
  • 66