Recently, I had trouble persisting an object with Hibernate. I would like to depict my journey along the well-known example of documenting the microphones of a karaoke bar! Here, we have two microphones (of course, a yellow one to the obligatory black one!) for our bar called Monster Karaoke. We create the microphones first, then the bar and ultimately save it to a corresponding JPA repository.
Microphone blackMicrophone = Microphone.builder() .microphoneId(UUID.randomUUID()) .color("black") .build(); Microphone yellowMicrophone = Microphone.builder() .microphoneId(UUID.randomUUID()) .color("yellow") .build(); List<Microphone> microphoneList = Arrays.asList(blackMicrophone, yellowMicrophone); KaraokeBar karaokeBar = KaraokeBar.builder() .barId(UUID.randomUUID()) .name("Monster Karaoke") .microphoneList(microphoneList) .build(); karaokeBarRepository.save(karaokeBar);
A KaraokeBar object contains a list of Microphone objects which is annotated with Hibernate's
@OneToMany. But when executing the code, following happens:
Request processing failed; nested exception is org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find de.schmowser.radio.domain.Microphone with id fb133ab8-72ee-4bf4-ac5f-4701cb99e766;
Couldn't be found? I see that we need to cascade persistence such that our magnificiently painted micros get persisted when the KaraokeBar instance is. But even after adding
@OneToMany(cascade = CascadeType.PERSIST) private List<Microphone> microphoneList;
javax.persistence.EntityNotFoundException convinces us that the microphones are still not committed for persistence. And only now, I remember that we know someone who is responsible for persisting any entities to databases. It's the EntityManager within the
@PersistenceContext. After autowiring it and using entityManager.perist, this pops up:
No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call
Yeah, we have to annotate the method with
@Transactional denoting that all transactional matters are handled in the background automatically! Furthermore, we have to define the owner side object (KaraokeBar) first, then setting it in our Microphone instances, and then setting the list in karaokeBar. Otherwise, this exception occurs.
org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : de.schmowser.radio.domain.Microphone.karaokeBar -> de.schmowser.radio.domain.KaraokeBar
It feels like we are close to letting our customers know which microphones our bar offers. Curiously, without the Repository.save command, the following code snippet compiles...
List<Microphone> microphoneList = Arrays.asList(blackMicrophone, yellowMicrophone); karaokeBar.setMicrophoneList(microphoneList); entityManager.persist(karaokeBar); entityManager.persist(blackMicrophone); entityManager.persist(yellowMicrophone); karaokeBarRepository.save(karaokeBar);
...while with saving, it does not. Having the last line included rewards us with a
java.lang.UnsupportedOperationException. This one is actually quite hard to decipher. Somehow, Hibernate neither likes us nor unmodifiable lists. Therefore, we give him what it wants: Something to modify.
List<Microphone> microphoneList = new ArrayList<>(); microphoneList.add(blackMicrophone); microphoneList.add(yellowMicrophone);
Hibernate is merciful and gives us what we desire!
Hibernate: insert into karaoke_bar (name, karaokebar_id) values (?, ?) Hibernate: insert into microphone (color, karaokebar_id, microphone_id) values (?, ?, ?) Hibernate: insert into microphone (color, karaokebar_id, microphone_id) values (?, ?, ?)
Is this anyway near your experience with Hibernate? How do you handle this storm of exceptions - generally and maybe more effectively?