I'm getting the following error when looking up a persisted object which has a Map
attribute:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: [package].MapKey; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: [package].MapKey
Most explanations I found refer to adding CascadeType.ALL
, which I have done.
The error appears only when I execute a custom query, not with the findById
method:
EntityWithMap saved = service.save(entity);
assertEquals(entity.getMap(), service.findById(saved.getId()).get().getMap()); //No error
assertEquals(entity.getMap(), service.findByName("test entity").get(0).getMap()); //InvalidDataAccessApiUsageException
EntityWithMap:
@Entity
public class EntityWithMap {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "mapping_mapkey_mapvalue",
joinColumns = {@JoinColumn(name = "value_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "entity_id", referencedColumnName = "id")})
@MapKeyJoinColumn(name = "key_id", referencedColumnName = "id")
private Map<MapKey, MapValue> map = new HashMap<>();
private String name;
public EntityWithMap(String name) {
this.name = name;
}
public Map<MapKey, MapValue> getMap() {
return map;
}
public Long getId() {
return id;
}
public void addToMap(MapKey key, MapValue value) {
map.put(key, value);
}
}
MapKey:
@Entity
public class MapKey {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
}
MapValue:
@Entity
public class MapValue {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
}
Test class:
@DataJpaTest
@Import(EntityWithMapService.class)
public class PersistMappingTest {
@Autowired private EntityWithMapService service;
@Test
public void testPersistence() {
EntityWithMap entity = new EntityWithMap("test entity");
entity.addToMap(new MapKey(), new MapValue());
entity.addToMap(new MapKey(), new MapValue());
entity.addToMap(new MapKey(), new MapValue());
EntityWithMap saved = service.save(entity);
assertEquals(entity.getMap(), service.findById(saved.getId()).get().getMap()); //No error
assertEquals(entity.getMap(), service.findByName("test entity").get(0).getMap()); //InvalidDataAccessApiUsageException
}
}
EntityWithMapService:
@Service
public class EntityWithMapService {
private EntityWithMapRepository repository;
public EntityWithMapService(EntityWithMapRepository repository) {
this.repository = repository;
}
public EntityWithMap save(EntityWithMap entity) {
return repository.save(entity);
}
public Optional<EntityWithMap> findById(Long id) {
return repository.findById(id);
}
public List<EntityWithMap> findByName(String name) {
return repository.findByName(name);
}
}
EntityWithMapRepository:
@Repository
public interface EntityWithMapRepository extends JpaRepository<EntityWithMap, Long> {
@Query("FROM EntityWithMap e WHERE e.name = :name")
public List<EntityWithMap> findByName(@Param("name") String name);
}
There's a couple of things that seem to be off in your example.
PersistMappingTest
is trying to persist a record of EntityWithMap
with references to instance of MapKey
and MapValue
without persisting them first. You need to persist the MapKey
and MapValue
records before you can use them as references in the EntityWithMap
record. This might be the primary reason you get TransientObjectException
.Example (pseudo code):
MapKey mapKey1 = mapKeyService.save(new MapKey());
MapKey mapKey2 = mapKeyService.save(new MapKey());
MapKey mapKey3 = mapKeyService.save(new MapKey());
MapValue mapValue1 = mapValueService.save(new MapValue());
MapValue mapValue2 = mapValueService.save(new MapValue());
MapValue mapValue3 = mapValueService.save(new MapValue());
EntityWithMap entity = new EntityWithMap("test entity");
entity.addToMap(mapKey1, mapValue1);
entity.addToMap(mapKey2, mapValue2);
entity.addToMap(mapKey3, mapValue3);
NOTE: if it's intentional to not persist the MapKey
and MapValue
map in the DB and it's only for in-memory use then, try adding the @Transient
annotation to the map field in EntityWithMap
.
MapValue
entity is not referencing MapKey
at all. How can MapKey
be the key of MapValue
if MapValue
is not aware of it.Example (pseudo code):
@Entity
public class MapValue {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "mapkey_id")
private MapKey mapKey;
}
HashMap<>
in the declaration of the map in entity EntityWithMap
. JPA should do that for you. This could also be a reason you get the exception.Look at this article for more information: https://www.baeldung.com/hibernate-persisting-maps