传递的分离实体在Spring-Data中持久存在

Ily*_*lov 9 java hibernate spring-data

我在我的分贝两个表BrandProduct下一个简单的结构:

| 品牌| id PK |

| 产品| id 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 */
}
Run Code Online (Sandbox Code Playgroud)
@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 */
}
Run Code Online (Sandbox Code Playgroud)

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

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

    Brand findByBrand(String brand);
}
Run Code Online (Sandbox Code Playgroud)
public interface BrandService {

    Brand findByBrand(String brand);
}
Run Code Online (Sandbox Code Playgroud)
@Service
public class BrandServiceImpl implements BrandService {

    @Autowired
    private BrandRepository brandRepository;

    @Override
    public Brand findByBrand(String brand) {

        return brandRepository.findByBrand(brand);
    }
}
Run Code Online (Sandbox Code Playgroud)

和产品:

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}
Run Code Online (Sandbox Code Playgroud)
public interface ProductService {

    Product save(Product product);
}
Run Code Online (Sandbox Code Playgroud)
@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Override
    public Product save(Product product) {
        return productRepository.save(product);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

如果具有指定brandName的Brand对象不在我的数据库中,它可以正常工作.但如果是我得到:

PersistentObjectException: detached entity passed to persist
Run Code Online (Sandbox Code Playgroud)

为品牌.

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

IllegalStateException:
org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance - save the transient instance before flushing
Run Code Online (Sandbox Code Playgroud)

对于品牌(这真的不会感到惊讶).

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

Leo*_*ruz 18

简短回答:

级联注释没有问题.您不应该依赖自动级联并在服务层内手动实现此逻辑.

答案很长:

您有两种情况:

  • 场景1 - CascadeType.ALL +现有品牌=传递给持久化的分离实体
  • 场景2 - CascadeType.MERGE + new brand =在flushin之前保存瞬态实例

场景1发生是因为JPA在持久化PRODUCT(CascadeType.ALL)后试图保持BRAND.一旦BRAND存在,您就会出错.

场景2发生,因为JPA并没有试图坚持BRAND(CascadeType.MERGE)并且BRAND之前没有持久化.

很难找到解决方案,因为有很多抽象层.Spring数据提取JPA,它抽象出抽象JDBC等的Hibernate.

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

另一种解决方案是简短的回答.

例:

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

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

}
Run Code Online (Sandbox Code Playgroud)


Gag*_*lra 5

在方法中添加@Transactional 将整个讨论整合到一个 PersistenceContext 以及优化查询(更少的 hibernate 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);

}
Run Code Online (Sandbox Code Playgroud)