java 传递到 Spring-Data 中持久化的分离实体

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/40247030/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 05:03:12  来源:igfitidea点击:

Detached entity passed to persist in Spring-Data

javahibernatespring-data

提问by Ilya Orlov

I have two tables in my db Brandand Productwith the next simple structure:

我的数据库中有两个表,Brand并且Product具有下一个简单的结构:

| Brand | id PK |

| 品牌 | 身PK |

| Product | id PK | brand_id FK |

| 产品 | 身PK | 品牌_id FK |

and entities for that tables:

和该表的实体:

@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 */
}

As I use Spring-Data I have repository and service with implementation for Brand:

当我使用 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);
    }
}

and for Product:

和产品:

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

The goal is to save Product object. Brand object should be saved automatically if it doesn't exist in db or should be set to Product otherwise:

目标是保存 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);

It works fine if Brand object with specified brandName is not in my db. But if it is I get:

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

PersistentObjectException: detached entity passed to persist

for Brand.

为品牌。

I can change cascade type to MERGE and it will work fine. But if I run the code with MERGE cascade type and Brand object with specified brandName is not in my db I get

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

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

for Brand (that's really not surprised).

对于品牌(这真的不奇怪)。

What Cascade Type should be? Ot what I did wrong?

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

回答by Leonardo Cruz

Short answer:

简答:

There is no problem with your cascade annotation. You should not rely on automatic cascade and implement this logic by hand and inside your service layer.

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

Long answer:

长答案:

You have two scenarios:

你有两种情况:

  • Scenario 1 - CascadeType.ALL + existing brand = detached entity passed to persist
  • Scenario 2 - CascadeType.MERGE + new brand = save the transient instance before flushin
  • 场景 1 - CascadeType.ALL + 现有品牌 = 传递给持久化的分离实体
  • 场景 2 - CascadeType.MERGE + 新品牌 = 在刷新之前保存瞬态实例

Scenario 1 happens because JPA is trying to persist BRAND after persist PRODUCT (CascadeType.ALL). Once BRAND already exists you got an error.

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

Scenario 2 happend because JPA is not trying to persist BRAND (CascadeType.MERGE) and BRAND was not persisted before.

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

It's hard to figure out a solution because there are so many abstraction layers. Spring data abstracts JPA that abstracts Hibernate that abstracts JDBC and so on.

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

A possible solution would be use EntityManager.merge instead of EntityManager.persist so that CascadeType.MERGE could work. I belive you can do that re-implementing Spring Data save method. There is some reference about that here : Spring Data: Override save method

一个可能的解决方案是使用 EntityManager.merge 而不是 EntityManager.persist 以便 CascadeType.MERGE 可以工作。我相信您可以重新实现 Spring Data 保存方法。这里有一些参考:Spring Data: Override save method

Another solution would be the short answer.

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

Example:

例子:

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

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

}

回答by Gagandeep Kalra

Adding @Transactional to the method brings the entire discussion into one PersistenceContext alongwith optimising queries(fewer hibernate sql queries)

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

}