在 Spring Boot 中保存 Map 属性时出现 TransientObjectException
TransientObjectException when saving a Map attribute in Spring Boot
我在查找具有 Map
属性的持久对象时收到以下错误:
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
我找到的大多数解释都涉及添加 CascadeType.ALL
,我已经这样做了。
错误仅在我执行自定义查询时出现,而不是使用 findById
方法:
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);
}
}
地图键:
@Entity
public class MapKey {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
}
地图值:
@Entity
public class MapValue {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
}
测试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);
}
您的示例中有几处似乎不对。
- 您的测试
PersistMappingTest
正在尝试保存 EntityWithMap
的记录并引用 MapKey
和 MapValue
的实例,而不是先保存它们。您需要保留 MapKey
和 MapValue
记录,然后才能将它们用作 EntityWithMap
记录中的引用。这可能是您获得 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("test entity");
entity.addToMap(mapKey1, mapValue1);
entity.addToMap(mapKey2, mapValue2);
entity.addToMap(mapKey3, mapValue3);
注意:如果有意不在数据库中保留 MapKey
和 MapValue
映射并且仅供 in-memory 使用,请尝试添加 @Transient
注释到 EntityWithMap
.
中的地图字段
- 您
MapValue
实体根本没有引用 MapKey
。 MapKey
怎么可能是MapValue
的key,如果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
我在查找具有 Map
属性的持久对象时收到以下错误:
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
我找到的大多数解释都涉及添加 CascadeType.ALL
,我已经这样做了。
错误仅在我执行自定义查询时出现,而不是使用 findById
方法:
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);
}
}
地图键:
@Entity
public class MapKey {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
}
地图值:
@Entity
public class MapValue {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
}
测试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);
}
您的示例中有几处似乎不对。
- 您的测试
PersistMappingTest
正在尝试保存EntityWithMap
的记录并引用MapKey
和MapValue
的实例,而不是先保存它们。您需要保留MapKey
和MapValue
记录,然后才能将它们用作EntityWithMap
记录中的引用。这可能是您获得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("test entity");
entity.addToMap(mapKey1, mapValue1);
entity.addToMap(mapKey2, mapValue2);
entity.addToMap(mapKey3, mapValue3);
注意:如果有意不在数据库中保留 MapKey
和 MapValue
映射并且仅供 in-memory 使用,请尝试添加 @Transient
注释到 EntityWithMap
.
- 您
MapValue
实体根本没有引用MapKey
。MapKey
怎么可能是MapValue
的key,如果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