当前位置: 首页 > 知识库问答 >
问题:

JPA:同时大容量保存子实体和父实体

能文华
2023-03-14

我正在从经典Spring 4.3.2 Hibernate(HQL)迁移到Spring Boot 1.4.1 Hibernate(JPA)HikariCP连接池,并且在批量插入方面遇到问题。我需要保存1000到527 000个元素的大列表。

当我只保存一个实体的批次,没有任何依赖项(例如:类别类的5000个元素)时,一切都很好。
但是,如果我尝试同时保存子实体和父实体(网站只需为其设置类别),我会得到例外:org.hibernate.PeristentObjectException:传递给持久化的分离实体:com.example.entities.类别

但是当列表大小小于hibernate.jdbc.batch_size时,一切都很好!!两个实体都被正确保存,父实体和子实体。这看起来真的很奇怪!
我已经谷歌了足够多,在StackOverflow上搜索,但找不到具体解决我的问题的解决方案。我试图将连接池替换为Apache DBCP,甚至根本不包括一个),更改了Hibernate性能设置,将PostgreSQL db替换为H2-但没有结果。

错误可能在哪里?
是否可以在JPA中做我想要的事情?或者我应该单独保存实体(首先是父实体,然后是子实体)?另一种方法是从我的程序中排除flush()Clear(),但这对性能不好!
希望得到帮助。以下是详细信息。这就是我试图保存实体的方式:

    @Repository("experimentDao")
    @Transactional
    public class Dao {
        @PersistenceContext
        private EntityManager entityManager;

        @Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
        private int batchSize;

        public <T extends Entity> void bulkSave(Collection<T> entities) {
            int i = 0;
            for (Entity entity : entities) {
                persistOrMerge(entity);
                i++;
                if (i % batchSize == 0) {
                    // Flush a batch of inserts and release memory.
                    entityManager.flush();
                    entityManager.clear();
                }
            }
        }

    private <T extends Entity> void persistOrMerge(T entity) {
        if (entity.getId() == null) {
            entityManager.persist(entity);
        } else {
            entityManager.merge(entity);
        }
    }
 }

(示例位于:http://frightanic.com/software-development/jpa-batch-inserts/). 我得到以下stacktrace:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:803) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:784) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:771) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1186) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1175) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
    at com.example.DemoApplication.main(DemoApplication.java:24) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_101]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_101]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_101]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_101]
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) [idea_rt.jar:na]
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.example.entities.Category; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.entities.Category
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:299) ~[spring-orm-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) ~[spring-orm-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:491) ~[spring-orm-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) ~[spring-tx-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) ~[spring-tx-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) ~[spring-tx-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281) ~[spring-tx-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at com.example.Dao$$EnhancerBySpringCGLIB$$79470367.bulkSave(<generated>) ~[classes/:na]
    at com.example.DemoApplication.insertWebsites(DemoApplication.java:44) [classes/:na]
    at com.example.DemoApplication.run(DemoApplication.java:30) [classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) [spring-boot-1.4.1.RELEASE.jar:1.4.1.RELEASE]
    ... 11 common frames omitted
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.entities.Category
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:765) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:758) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:398) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:111) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:425) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:249) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:178) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:121) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753) ~[hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at sun.reflect.GeneratedMethodAccessor11.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_101]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_101]
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298) ~[spring-orm-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at com.sun.proxy.$Proxy62.persist(Unknown Source) ~[na:na]
    at com.example.Dao.persistOrMerge(Dao.java:36) ~[classes/:na]
    at com.example.Dao.bulkSave(Dao.java:24) ~[classes/:na]
    at com.example.Dao$$FastClassBySpringCGLIB$$2ada4001.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.3.RELEASE.jar:4.3.3.RELEASE]
    ... 21 common frames omitted


网站和类别实体具有一对一的关系,类别是父级。我还有一个抽象的超类,它不应该被持久化:

public abstract class Entity {
    public abstract Long getId();
}

@javax.persistence.Entity
@Table(name = "websites")
public class Website extends Entity implements Serializable {
    @Id
    @GeneratedValue(strategy = SEQUENCE)
    @Column(name = "website_id")
    private Long id;

    @Column(name = "url", unique = true)
    private String url;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="category_id")
    private Category category;
    // getters and setters, toString
}

@javax.persistence.Entity
@Table(name = "categories")
public class Category extends Entity implements Serializable {
    @Id
    @GeneratedValue(strategy = SEQUENCE)
    @Column(name = "category_id")
    private Long id;

    @Column(name = "category_name", unique = true)
    private String categoryName;
    // getters, setters, toString
}

现在我想保存属于同一类别的5000个网站。这是我如何批量插入:

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    private Dao dao;

    @Autowired
    public void setDao(Dao dao) {
        this.dao = dao;
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... strings) throws Exception {
        insertWebsites();
    }

    private void insertWebsites() {
        List<Website> entities = new ArrayList<>(5000);
        Category category = new Category();
        category.setCategoryName("my category");

        for (int i = 0; i < 5000; i++) {
            Website website = new Website();
            website.setUrl("http://example.com" + i);
            website.setCategory(category);
            entities.add(website);
        }
        dao.bulkSave(entities);
    }
}

设置和参数:
(application.properties)

spring.datasource.driver-class-name = org.postgresql.Driver
spring.datasource.url = jdbc:postgresql://localhost:5432/spring_web
spring.datasource.username = postgres
spring.datasource.password = postgresql
spring.datasource.type = com.zaxxer.hikari.HikariDataSource

# Hibernate
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQL94Dialect
spring.jpa.properties.hibernate.max_fetch_depth = 3
spring.jpa.properties.hibernate.jdbc.fetch_size = 30
spring.jpa.properties.hibernate.jdbc.batch_size = 30
spring.jpa.properties.hibernate.order_inserts = true
spring.jpa.properties.hibernate.order_updates = true
spring.jpa.properties.hibernate.cache.use_second_level_cache = false

(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <!--<scope>runtime</scope>-->
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>2.5.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

很抱歉我太啰嗦了,我试着给出了所有的细节。期待得到任何帮助!


共有1个答案

澹台新知
2023-03-14

一旦执行,问题就会清楚:

  • 创建一个类别
  • batch_size网站被创建并持久化;它们都指向上面创建的单个类别
    • 一旦第一个网站被持久化,由于关系中的级联,该类别也被持久化

    鉴于这种关系,我不相信bulkSave()能以这种通用方式工作。如果要批量插入网站,请执行以下操作:

    @Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
    private int batchSize;
    
    @PersistenceContext
    private EntityManager em; // you will need the EntityManager
    
    private void insertWebsites() {
        Category category = new Category();
        category.setCategoryName("my category");
        // persist it separately to keep the id for later
        em.persist(category);
        Long categoryId = category.getId();
    
        for (int i = 1; i <= 5000; i++) {
            Website website = new Website();
            website.setUrl("http://example.com" + i);
            website.setCategory(category);
            if (i % batchSize == 0) {
                em.flush();
                em.clear();
                // get an attached reference to the category (you know
                // that it exists) without querying the database again
                category = em.getReference(Category.class, categoryId);
            }
        }
    }
    

 类似资料:
  • 我正在尝试开发一个小型电子商务项目。我有一个篮子和篮子项目实体。我只是想当我为客户保存购物篮时,我想把购物篮项目保存在数据库中。我认为我不应该为篮子项目创建存储库。我应该能够保存篮子项目,同时使用篮子存储库保存篮子。 在这里,我试图通过篮子服务获得一个篮子项目,并将其设置为篮子实体并保存它。 我有什么问题?我有个例外。

  • 问题内容: 我需要处理一个CSV文件,并且对于每个记录(行)都保留一个实体。现在,我这样做: 该方法基本上只是一个调用。CSV文件中大约有20,000个实体(行)。这是一种有效的方法吗?似乎很慢。使用会更好吗?这个解决方案有任何缺陷吗? 编辑 这是一个漫长的过程(超过400秒)和我都尝试的解决方案,与和。两者花费的时间大致相同(459s与443s)。问题是,像这样一个接一个地保存实体是否最佳。据我

  • 我可能需要一些帮助,因为我被这些文件弄糊涂了: 我有一个JPA母公司: 和孩子: 如果我只是增加联系人,我希望JPA能够建立这种关系。 e、 g: 现在发生的是: 触点插入DB(良好) 使用providerX。联系人中的id。提供者列(良好) provider\u contact表中未显示任何关系条目 我知道我可以将联系人设置为提供者的属性<代码>providerX。联系人。添加(c)。。。回购。

  • 在使用hibernate和jpa的spring mvc项目中,我有一个实体和一个实体。每个可以有许多,但每个只能有一个。如何为现有的保存新的? 我读过很多关于这个的帖子。如果我试图保存新的,就会出现无法保存分离实体的错误。但如果我试图保存现有的,则不会保存该角色。我读了这篇帖子,建议将hibernate注释移动到getter,但当我这样做时,我会收到一个引用未知属性的hibernate映射错误。这

  • 问题内容: 我在父方使用批注具有一对一关系。现在,我想自己保存子实体。 例如,我有和作为孩子的实体,我需要保存(父的id属性设置为之后的课程)。但是,当使用这种安排时,我在下面列出了一个例外… 为什么hibernate不允许这样做的任何想法?更清楚地说,我的代码如下… ParentEntity: ChildEntity: 我尝试保存的方式是… 关于如何尝试保存子实体,任何指针将不胜感激。 问题答案

  • 我使用的是Spring数据jpa。将子实体添加到父实体后,我将父实体保存到数据库。我想得到孩子的身份证,但我发现我得到的是空的。 我在getId()方法中添加了@GeneratedValue(Strategy=GenerationType.IDENTITY),但它不起作用。 以下是模型: 父实体已经在数据库中,所以我直接找到它,父存储库entends Jpa列举 这里是我的测试代码: 我得到的输出