In my Spring-Boot app I'm using GORM via:
implementation 'org.grails:gorm-hibernate5-spring-boot:8.0.3'
All domain classes are annotated with GORM @Entity
and are initialized with new HibernateDatastore( cfg, classes )
and work like a charm.
I also created a delegating helper class to call dynamic GORM methods from java:
class JavaGORMHelper {
static <T extends GormEntity> void withTransaction( Class<T> clazz, Consumer<TransactionStatus> action ) {
clazz.withTransaction{ action it }
}
static <T extends GormEntity> T findBy( Class<T> clazz, String what, Object... args ) {
clazz."findBy$what"( *args )
}
// other 40 methods
}
Now I want to call those GORM methods from java service and for that I would use Spring's @Transactional
:
import org.springframework.transaction.annotation.Transactional;
@Service
public class JobService {
@PostConstruct
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public void init() {
JavaGORMHelper.list(Job.class).forEach(job -> foo( job ));
}
}
The code is throwing a No Session found for current thread
exception:
[main] 16:23:37.303 ERROR o.s.boot.SpringApplication - Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jobController': Unsatisfied dependency expressed through field 'jobService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobService': Invocation of init method failed; nested exception is org.springframework.dao.DataAccessResourceFailureException: Could not obtain current Hibernate Session; nested exception is org.hibernate.HibernateException: No Session found for current thread
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:659)
... 20++ common frames omitted
Caused by: org.springframework.dao.DataAccessResourceFailureException: Could not obtain current Hibernate Session; nested exception is org.hibernate.HibernateException: No Session found for current thread
at org.grails.orm.hibernate.GrailsHibernateTemplate.getSession(GrailsHibernateTemplate.java:335)
at org.grails.orm.hibernate.GrailsHibernateTemplate.doExecute(GrailsHibernateTemplate.java:284)
at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:241)
at org.grails.orm.hibernate.GrailsHibernateTemplate.execute(GrailsHibernateTemplate.java:120)
... 32++ common frames omitted
Caused by: org.hibernate.HibernateException: No Session found for current thread
at org.grails.orm.hibernate.GrailsSessionContext.currentSession(GrailsSessionContext.java:112)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:508)
at org.grails.orm.hibernate.GrailsHibernateTemplate.getSession(GrailsHibernateTemplate.java:333)
... 56 common frames omitted
I tried exposing transactionManager
and sessionFactory
from HibernateDatastore
as spring beans with no success.
If I wrap the method call into JavaGORMHelper.withTransaction(Job.class, tx -> {..});
it works fine.
Also it works fine if I convert the java service to groovy and use @grails.gorm.transactions.Transactional
.
How to make the @Transactional
work for GORM invocations from java? What am I missing?
It turns out, that no additional configuration steps for the domain classes are needed.
If the domain classes are located within the project, they are found and initialized automatically and the @Transactional
also works fine:
@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)
@RestController
public class Application {
final Random RND = new Random();
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@EventListener(ApplicationReadyEvent.class)
@Transactional
public void init() {
System.out.println( "Count before = " + Person.cnt() );
Person p1 = new Person();
p1.setName("aaa");
p1.setAge(11);
p1.save();
System.out.println( "Count after = " + Person.cnt() );
}
}
There are some gotchas on the way though...
1st, the JPA-auto-configurer clashes with the counterpart of hibernate, hence the exclusion of the former inside @SpringBootApplication
.
2nd, if the groovy and java classes reside in the same project and joint compilation is used, the domain class implementing GormEntity
:
@Entity
class Person implements GormEntity<Person> {
long id
String name
int age
}
fails with CompileException like:
> Task :compileGroovy
C:\workspace\gorm-spring-repro\build\tmp\compileGroovy\groovy-java-stubs\gorm\spring\repro\Person.java:9: error: Person is not abstract and does not override abstract method addTo(String,Object) in Person
@grails.gorm.annotation.Entity() public class Person
^
1 error
To work the problem around I moved the domain classes into a separate sub-project
and added the following static delegating method to the domain class, as Java doesn't see the trait static methods from GormStaticApi
:
@Entity
class Person implements GormEntity<Person> {
...
static int cnt() {
count()
}
}
Then upon starting the setup works like charm:
2024-02-15 12:10:49.044 INFO 26048 --- [ main] gorm.spring.repro.Application : Started Application in 3.876 seconds (JVM running for 4.251)
Hibernate: select count(person0_.id) as col_0_0_ from person person0_ limit ?
Count before = 0
Hibernate: insert into person (id, version, age, name) values (default, ?, ?, ?)
Hibernate: insert into person (id, version, age, name) values (default, ?, ?, ?)
Hibernate: select count(person0_.id) as col_0_0_ from person person0_ limit ?
Count after = 2