英文:
TransientObjectException when saving a Map attribute in Spring Boot
问题
以下是您提供的内容的中文翻译:
我在查找具有Map
属性的持久化对象时遇到以下错误:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: 对象引用了未保存的临时实例 - 在刷新之前保存临时实例:[package].MapKey;嵌套异常是java.lang.IllegalStateException: org.hibernate.TransientObjectException: 对象引用了未保存的临时实例 - 在刷新之前保存临时实例:[package].MapKey
我找到的大多数解释都涉及添加CascadeType.ALL
,我已经添加了。
仅当我执行自定义查询时才会出现错误,而使用findById
方法时不会出现错误:
EntityWithMap saved = service.save(entity);
assertEquals(entity.getMap(), service.findById(saved.getId()).get().getMap()); // 没有错误
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;
}
测试类:
@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()); // 没有错误
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);
}
英文:
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);
}
答案1
得分: 0
以下是翻译好的内容:
- 您的测试
PersistMappingTest
正在尝试持久化EntityWithMap
的记录,并引用了未首先持久化的MapKey
和MapValue
实例。您需要在将它们用作EntityWithMap
记录中的引用之前持久化MapKey
和MapValue
记录。这可能是您遇到TransientObjectException
的主要原因。
示例(伪代码):
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("测试实体");
entity.addToMap(mapKey1, mapValue1);
entity.addToMap(mapKey2, mapValue2);
entity.addToMap(mapKey3, mapValue3);
注意:如果在数据库中不意图持久化 MapKey
和 MapValue
映射,并且仅用于内存中使用,则尝试在 EntityWithMap
中的映射字段上添加 @Transient
注解。
- 您的
MapValue
实体根本没有引用MapKey
。如果MapValue
不知道MapKey
,那么如何将MapKey
作为MapValue
的键。
示例(伪代码):
@Entity
public class MapValue {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "mapkey_id")
private MapKey mapKey;
}
- 在实体
EntityWithMap
的映射声明中,您不需要创建新的HashMap<>
实例。JPA 应该会为您执行此操作。这也可能是您遇到异常的原因之一。
请查阅以下文章以获取更多信息:
https://www.baeldung.com/hibernate-persisting-maps
英文:
There's a couple of things that seem to be off in your example.
- Your test
PersistMappingTest
is trying to persist a record ofEntityWithMap
with references to instance ofMapKey
andMapValue
without persisting them first. You need to persist theMapKey
andMapValue
records before you can use them as references in theEntityWithMap
record. This might be the primary reason you getTransientObjectException
.
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
.
- You
MapValue
entity is not referencingMapKey
at all. How canMapKey
be the key ofMapValue
ifMapValue
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;
}
- You shouldn't need to create a new instance of
HashMap<>
in the declaration of the map in entityEntityWithMap
. 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论