首页 > 解决方案 > 在 Spring Boot 中保存 Map 属性时出现 TransientObjectException

问题描述

查找具有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

实体与地图:

@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;
    
}

测试类:

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

}

标签: javaspring-bootpersistence

解决方案


在您的示例中,有几件事似乎不正确。

  1. 您的测试PersistMappingTest正在尝试EntityWithMap使用对实例的引用来持久化记录,MapKeyMapValue不是先持久化它们。您需要保留MapKeyMapValue记录,然后才能将它们用作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);

注意:如果有意不在数据库中保留MapKeyandMapValue映射并且它仅用于内存中使用,请尝试将@Transient注释添加到EntityWithMap.

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

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


推荐阅读