Inserting 50k rows with Spring Data and JPA I am using Spring Data JPA with Spring boot application. My requirement is to insert 50K rows in JPA entity table in one hour or less. 

I have 3 entities A, B and C.  Entity A has a one to many association with entity B and a one to one association with entity C.

@Entity
public class A {
    @OneToMany(mappedBy = "chipJobItem", cascade = {
            CascadeType.MERGE,
            CascadeType.PERSIST,
            CascadeType.REFRESH,
            CascadeType.DETACH }, orphanRemoval = true, fetch = FetchType.LAZY)
    private List&lt;B&gt; values;
}

@Entity 
public class B {
    @ManyToOne(fetch = FetchType.LAZY, cascade = {
        CascadeType.MERGE,
        CascadeType.PERSIST,
        CascadeType.REFRESH,
        CascadeType.DETACH })
    @JoinColumn(name = "fk_product_instance_id", referencedColumnName = "id", foreignKey = @ForeignKey(name = "DATAITEMS_FK"))
    private A job;
}

@Entity
public class C {
    @ManyToOne(fetch = FetchType.LAZY, cascade = {
        CascadeType.MERGE,
        CascadeType.PERSIST,
        CascadeType.REFRESH,
        CascadeType.DETACH })
    @JoinColumn(name = "fk_product_instance_id", referencedColumnName = "id", foreignKey = @ForeignKey(name = "DATAITEMS_FK"))
    private A job;
}


I have also set the JDBC batch size = 100, it improves the performance a little. 

Need help improving the performance of the JPA insert.
To state the problem:
You have a large sum of operations that are repetitive and mostly similar(?) that are long running, and you want to reduce that run time.

If I have that right:

If order is not a concern You could consider inserting in parallel. If you can represent the 50,000 updates as a List, say, List&lt;InsertCriteriaPojo&gt;, and can represent the insert as a Consumer lambda, like so: ((InsertCriteriaPojo item) -&gt; myDao.insert(item)), then this could be a one line call:

List&lt;InsertCriteriaPojo&gt; items = ...;

items.stream().parallel().map((InsertCriteriaPojo item) -&gt; myDao.insert(item));


This creates a Stream of InsertCriteriaPojos, converts that to a ParallelStream, and them maps each item in the List to a Consumer and runs the Consumer/item tasks in a set of threads belonging to the common ForkJoinPool



While short, there are problems with this

The main problem as explained here is that you can't specify an Executor to ParallelStreams (as of Java 1.8*).

UPDATE: The above is inaccurate. This can be done like so:

List&lt;InsertCriteriaPojo&gt; items = ...;
ForkJoinPool customThreadPool = new ForkJoinPool(threadCount);
customThreadPool.submit(
    () -&gt; items.stream().parallel().map(
        (InsertCriteriaPojo item) -&gt; myDao.insert(item)
    )
).get;




Another, more complex way to run these tasks in parallel is to use an Executor you configure:

@Bean(name = "myExecutor")
public Executor executor(){
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(yourProperties.getCorePoolCount());
    executor.setMaxPoolSize(yourProperties.getMaxPoolCount());
    executor.setQueueCapacity(yourProperties.getMaxQueueSize());
    executor.setThreadNamePrefix(yourProperties.getThreadNamePrefix());
    executor.initialize();
    return threadPoolTaskExecutor;
}


To run your consumers asynchronously:

...
@Autowire
@Qualifier("myExecutor")
Executor executor;
...
List&lt;InsertCriteriaPojo&gt; items = ...
//Make a Future for each task
List&lt;CompletableFuture&lt;Void&gt;&gt; asyncTaskFutures = list.stream().map(
    (InsertCriteriaPojo item) -&gt;{
        CompletableFuture.runAsync(
            () -&gt; myDao.insert(item), executor
        ).handle(
            (asyncVoidFuture, possibleError)-&gt;{
                if(possibleError != null){ //Handle your errors here }
                else{return asyncVoidFuture;}
        })
    }
).collect(Collectors.toList());

//Create a Future that is complete when allOf your task futures are complete
CompletableFuture&lt;Void&gt; process = CompletableFuture.allOf(
    asyncTaskList.toArray(new CompletableFuture[asyncTaskList.size()])
).thenRun(() -&gt; asyncTaskList.stream().map(future -&gt; future.join()));

//Complete the process-level future, blocks until complete
process.get();


While significantly more complex, the second is a more configurable and reusable way to solve your problem, but depending on your case either approach could serve your goal.