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