1

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
Archmede
  • 1,592
  • 2
  • 20
  • 37
  • Why are you not defining the FrameRepository as an interface and let Micronaut Data do the querying for you by defining a finder method called `List findAllByState(FrameState state)`? – saw303 Jun 30 '21 at 18:49
  • @saw303 In reality the query is much more complex. I've simplified it but at the same time tried to keep the it as similar as possible to better describe the problem – Archmede Jun 30 '21 at 18:50
  • Can you post the `causedBy` part of the stacktrace? – PhilBa Jul 21 '21 at 12:03
  • @PhilBa this is all the information I have. The cause is null – Archmede Jul 23 '21 at 17:49
  • My solution in the end was to accept the parameter as a string and later convert to an enum – Archmede Nov 07 '22 at 20:46

1 Answers1

0

I´d guess it´s not connected to the Enum, it´s because of the way how Hibernate manages sessions and connections.

In very simple words: by annotating a function as @Transactional a new session is opened, and you probably need a session for the actions you want to perform in the repository function.

I´d bet that it´s also enough to just annotate the Repository.findFrames function as @Transactional and leave the rest as is.

stk
  • 6,311
  • 11
  • 42
  • 58
  • yeah but my service is already marked as `@Transactional`, I must mark my controller as `@Transactional` to make it work – Archmede Jul 19 '21 at 14:54