【问题标题】:Detached entity passed to persist in Spring-Data传递的分离实体在 Spring-Data 中持久化
【发布时间】:2017-03-07 22:04:21
【问题描述】:

我的数据库中有两个表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);
    }
}

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

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

如果具有指定品牌名称的品牌对象不在我的数据库中,它可以正常工作。但如果是我得到:

PersistentObjectException: detached entity passed to persist

品牌。

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

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

对于品牌(这并不奇怪)。

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

【问题讨论】:

  • 很可能是由于缺少交易。产品来自哪里?发布您构建和保存产品的整个类。

标签: java hibernate spring-data


【解决方案1】:

在方法中添加@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);

}

【讨论】:

    【解决方案2】:

    简答:

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

    长答案:

    你有两种情况:

    • 场景 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 保存方法。这里有一些参考:Spring Data: Override save method

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

    例子:

    @Override
    public Product save(Product product, String brandName) {
    
        Brand brand = brandService.findByBrand(brandName);
        if (brand == null) {
            brand = brandService.save(brandName);
        }
        return productRepository.save(product);
    
    }
    

    【讨论】:

      猜你喜欢
      • 2020-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-10
      • 2013-06-26
      • 2018-06-09
      • 1970-01-01
      相关资源
      最近更新 更多