分离的实体传递以保留在 Spring-Data 中

2022-09-02 12:30:54

我的数据库中有两个表,下一个简单结构:BrandProduct

|品牌|标识 PK |

|产品|标识 PK |brand_id FK |

和该表的实体:

@Entity
@Table(name = "Brand")
public class Brand {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "brand")
    private String brand;

    /* getters and setters */
}

@Entity
@Table(name = "Product")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "brand_id")
    private Brand brand;

    /* getters and setters */
}

当我使用Spring-Data时,我有用于Brand实现的存储库和服务:

@Repository
public interface BrandRepository extends JpaRepository<Brand, Long> {

    Brand findByBrand(String brand);
}

public interface BrandService {

    Brand findByBrand(String brand);
}

@Service
public class BrandServiceImpl implements BrandService {

    @Autowired
    private BrandRepository brandRepository;

    @Override
    public Brand findByBrand(String brand) {

        return brandRepository.findByBrand(brand);
    }
}

对于产品:

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}

public interface ProductService {

    Product save(Product product);
}

@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Override
    public Product save(Product product) {
        return productRepository.save(product);
    }
}

目标是保存产品对象。如果品牌对象在 db 中不存在,则应自动保存该对象,否则应将其设置为 Product:

Brand brand = brandService.findByBrand(brandName);
if (brand == null) {
    brand = new Brand();
    brand.setBrand("Some name");
}
product.setBrand(brand);
productService.save(product);

如果具有指定品牌名称的 Brand 对象不在我的 db 中,则工作正常。但如果是,我得到:

PersistentObjectException: detached entity passed to persist

对于品牌。

我可以将级联类型更改为MERGE,它将正常工作。但是,如果我使用MERGE级联类型运行代码,并且具有指定品牌名称的Brand对象不在我的数据库中,我得到

IllegalStateException:
org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance - save the transient instance before flushing

对于品牌(这真的不令人惊讶)。

级联类型应该是什么?哎呀,我做错了什么?


答案 1

简短的回答:

级联注释没有问题。不应依赖自动级联,而是手动在服务层内部实现此逻辑。

长答案:

您有两种方案:

  • 方案 1 - CascadeType.ALL + 现有品牌 = 传递给持久性的分离实体
  • 方案 2 - CascadeType.MERGE + 新品牌 = 在刷新之前保存瞬态实例

场景 1 的发生是因为 JPA 试图在持久化 PRODUCT (CascadeType.ALL) 之后持久化 BRAND。一旦BRAND已经存在,你就会得到一个错误。

发生方案 2 是因为 JPA 没有尝试持久化 BRAND (CascadeType.MERGE),并且 BRAND 以前没有持久化。

很难找出解决方案,因为有太多的抽象层。Spring数据抽象JPA,抽象Hibernate,抽象JDBC等等。

一个可能的解决方案是使用EntityManager.merge而不是EntityManager.persist,以便CascadeType.MERGE可以工作。我相信你可以重新实现Spring Data save方法。这里有一些关于此的参考:弹簧数据:覆盖保存方法

另一个解决方案是简短的答案。

例:

@Override
public Product save(Product product, String brandName) {

    Brand brand = brandService.findByBrand(brandName);
    if (brand == null) {
        brand = brandService.save(brandName);
    }
    return productRepository.save(product);

}

答案 2

在方法中添加@Transactional,将整个讨论带入一个持久性上下文,同时优化查询(更少的休眠SQL查询)

@Override
@Transactional
public Product save(Product product, String brandName) {

    Brand brand = brandService.findByBrand(brandName);
    if (brand == null) {
        brand = brandService.save(brandName);
    }
    return productRepository.save(product);

}

推荐