在 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);

}

您的示例中有几处似乎不对。

  1. 您的测试 PersistMappingTest 正在尝试保存 EntityWithMap 的记录并引用 MapKeyMapValue 的实例,而不是先保存它们。您需要保留 MapKeyMapValue 记录,然后才能将它们用作 EntityWithMap 记录中的引用。这可能是您获得 TransientObjectException.
  2. 的主要原因

例子(伪代码):

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);

注意:如果有意不在数据库中保留 MapKeyMapValue 映射并且仅供 in-memory 使用,请尝试添加 @Transient 注释到 EntityWithMap.

中的地图字段
  1. MapValue 实体根本没有引用 MapKeyMapKey怎么可能是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;
    
}
  1. 您不需要在实体 EntityWithMap 的映射声明中创建 HashMap<> 的新实例。 JPA 应该为你做到这一点。这也可能是您获得异常的原因。

查看这篇文章了解更多信息: https://www.baeldung.com/hibernate-persisting-maps