I'm writing a Micronaut application which uses Hibernate to access the database. The issue I'm running into occurs when I try to query an entity using an enum
value. If I pass an instance of an enum into the repo then everything works fine. But if I pass an instance of the enum to the service layer (which is marked as @Transactional
) then I get a Hibernate exception. I'm not sure why since I don't believe the enum is a Hibernate managed bean.
I'll post a simplified example.
Controller Method:
@Get("/api/frames")
@TransactionalAdvice(readOnly = true)
@Transactional
fun findFrames(): HttpResponse<Frame> {
val response = frameService.findFrames(FrameState.SUCCESS)
return HttpResponse.ok(response)
}
Note that if the above method isn't marked as @Transactional
then this endpoint will result in a Hibernate exception, and I can't figure out why this is the case.
Service Method:
@TransactionalAdvice(readOnly = true)
@Transactional
open fun findFrames(
state: FrameState
): List<Frame> {
return frameRepository.findFrames(state)
}
Notice that this is marked transactional too.
Repository Method (class is annotated with @Repository
):
fun findFrames(
state: FrameState?
): List<Frame> {
val criteriaBuilder = entityManager.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(Frame::class.java)
val root = criteriaQuery.from(Frame::class.java)
val predicates = mutableListOf<Predicate>()
state?.let { predicates.add(criteriaBuilder.equal(root.get<FrameState>("state"), state)) }
criteriaQuery.select(root).where(*predicates.toTypedArray())
val query = entityManager.createQuery(criteriaQuery)
return query.resultList
}
and finally the Entity:
@Entity
@Table(name = "frame")
data class Frame(
@Column(nullable = false)
var title: String,
@Column(nullable = false)
var width: Short,
@Column(nullable = false)
var height: Short,
@Enumerated(EnumType.STRING)
@Column(nullable = false)
var state: FrameState,
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "frame_id", insertable = false, nullable = false, updatable = false)
var id: Int = 0
}
And here is the exception that gets thrown when the controller method isn't marked as @Transactional
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
at io.micronaut.transaction.hibernate5.MicronautSessionContext.currentSession(MicronautSessionContext.java:100)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:479)
at io.micronaut.configuration.hibernate.jpa.TransactionalSessionInterceptor.intercept(TransactionalSessionInterceptor.java:56)
at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:96)
at io.micronaut.configuration.hibernate.jpa.TransactionalSession$Intercepted.getCriteriaBuilder(Unknown Source)
at com.example.repository.FrameRepository.findFrames(FrameRepository.kt:27)
at com.example.service.FrameService.findFrames(FrameService.kt:73)
at com.example.controller.FrameController.findFrames(FrameController.kt:168)
at com.example.controller.$FramesControllerDefinition$Intercepted.$$access$$findFrames(Unknown Source)
at com.example.controller.$FrameControllerDefinition$$exec3.invokeInternal(Unknown Source)
at io.micronaut.context.AbstractExecutableMethod.invoke(AbstractExecutableMethod.java:151)
at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:87)
at io.micronaut.validation.ValidatingInterceptor.validateReturnExecutableValidator(ValidatingInterceptor.java:152)
at io.micronaut.validation.ValidatingInterceptor.intercept(ValidatingInterceptor.java:100)
at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:96)
at com.example.controller.$FrameControllerDefinition$Intercepted.findFrames(Unknown Source)
at com.example.controller.$FrameControllerDefinition$$exec3.invokeInternal(Unknown Source)
at ...
My question is, why do I need to mark my controller method as @Transactional
if FrameState
is just an enum and not a Hibernate managed bean?
Note, the exception is thrown when this line is executed:
val criteriaBuilder = entityManager.criteriaBuilder