在Spring Boot中保存Map属性时出现TransientObjectException。

huangapple go评论78阅读模式
英文:

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(&quot;test entity&quot;).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 = &quot;mapping_mapkey_mapvalue&quot;, 
      joinColumns = {@JoinColumn(name = &quot;value_id&quot;, referencedColumnName = &quot;id&quot;)},
      inverseJoinColumns = {@JoinColumn(name = &quot;entity_id&quot;, referencedColumnName = &quot;id&quot;)})
    @MapKeyJoinColumn(name = &quot;key_id&quot;, referencedColumnName = &quot;id&quot;)
	private Map&lt;MapKey, MapValue&gt; map = new HashMap&lt;&gt;();

	private String name;
	
	public EntityWithMap(String name) {
		this.name = name;
	}

	public Map&lt;MapKey, MapValue&gt; 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(&quot;test entity&quot;);
		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(&quot;test entity&quot;).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&lt;EntityWithMap&gt; findById(Long id) {
		return repository.findById(id);
	}
	
	public List&lt;EntityWithMap&gt; findByName(String name) {
		return repository.findByName(name);
	}
	
}

EntityWithMapRepository:

@Repository
public interface EntityWithMapRepository extends JpaRepository&lt;EntityWithMap, Long&gt; {
	
	@Query(&quot;FROM EntityWithMap e WHERE e.name = :name&quot;)
	public List&lt;EntityWithMap&gt; findByName(@Param(&quot;name&quot;) String name);

}

答案1

得分: 0

以下是翻译好的内容:

  1. 您的测试 PersistMappingTest 正在尝试持久化 EntityWithMap 的记录,并引用了未首先持久化的 MapKeyMapValue 实例。您需要在将它们用作 EntityWithMap 记录中的引用之前持久化 MapKeyMapValue 记录。这可能是您遇到 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);

注意:如果在数据库中不意图持久化 MapKeyMapValue 映射,并且仅用于内存中使用,则尝试在 EntityWithMap 中的映射字段上添加 @Transient 注解。

  1. 您的 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;
    
}
  1. 在实体 EntityWithMap 的映射声明中,您不需要创建新的 HashMap<> 实例。JPA 应该会为您执行此操作。这也可能是您遇到异常的原因之一。

请查阅以下文章以获取更多信息:
https://www.baeldung.com/hibernate-persisting-maps

英文:

There's a couple of things that seem to be off in your example.

  1. Your test 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(&quot;test entity&quot;);
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.

  1. You 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 = &quot;mapkey_id&quot;)
    private MapKey mapKey;
    
}
  1. You shouldn't need to create a new instance of HashMap&lt;&gt; 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

huangapple
  • 本文由 发表于 2020年9月16日 16:03:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/63915670.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定