CompletableFuture vs Spring Transactions












3















Idea



I have a processing method which takes in a list of items and processes them asynchronously using external web service. The process steps also persist data while processing. At the end of whole process, I want to persist the whole process along with each processed results as well.



Problem



I convert each item in the list into CompletableFuture and run a processing task on them, and put them back into an array of futures. Now using its .ofAll method (in sequence method) to complete future when all the submitted tasks are completed and return another CompletableFuture which holds the result.



When I want to get that result, I call .whenComplete(..), and would want to set the returned result into my entity as data, and then persist to the database, however the repository save call just does nothing and continues threads just continue running, it's not going past the repository save call.



 @Transactional
public void process(List<Item> items) {
List<Item> savedItems = itemRepository.save(items);

final Process process = createNewProcess();

final List<CompletableFuture<ProcessData>> futures = savedItems.stream()
.map(item -> CompletableFuture.supplyAsync(() -> doProcess(item, process), executor))
.collect(Collectors.toList());

sequence(futures).whenComplete((data, throwable) -> {
process.setData(data);
processRepository.save(process); // <-- transaction lost?
log.debug("Process DONE"); // <-- never reached
});
}


Sequence method



 private static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream().map(CompletableFuture::join).collect(Collectors.toList())
);
}


What is happening? Why is the persist call not passing. Is the thread that started the transaction not able to commit the transaction or where does it get lost? All the processed data returns fine and is all good. I've tried different transaction strategies, but how is it possible to control which thread is gonna finish the transaction, if it's the case?



Any advice?










share|improve this question

























  • what about sequence method, can you show it?

    – stjepano
    Feb 25 '16 at 13:46











  • It's jus glue code to perform allOf on the list of futures.

    – Vaelyr
    Feb 25 '16 at 13:54






  • 2





    I'm not 100% on this but it looks like the Transactional starts the transaction on method start and commits on method finish, Transactional does not wait for your callbacks to be fired. You could test that with simple thread.

    – stjepano
    Feb 25 '16 at 13:59











  • Well I assume so too. If I would do List<ProcessData> data = sequence(futures).join() and then repository.save(data), the method blocks and commits fine, I cannot block, but still would need a way to perform it in transaction from start to finish.

    – Vaelyr
    Feb 25 '16 at 14:07











  • @stjepano you are right Transactional start transaction at the begining of the execution and commit (or rollback if runtime error) at the end of the method.

    – JEY
    Feb 25 '16 at 14:11
















3















Idea



I have a processing method which takes in a list of items and processes them asynchronously using external web service. The process steps also persist data while processing. At the end of whole process, I want to persist the whole process along with each processed results as well.



Problem



I convert each item in the list into CompletableFuture and run a processing task on them, and put them back into an array of futures. Now using its .ofAll method (in sequence method) to complete future when all the submitted tasks are completed and return another CompletableFuture which holds the result.



When I want to get that result, I call .whenComplete(..), and would want to set the returned result into my entity as data, and then persist to the database, however the repository save call just does nothing and continues threads just continue running, it's not going past the repository save call.



 @Transactional
public void process(List<Item> items) {
List<Item> savedItems = itemRepository.save(items);

final Process process = createNewProcess();

final List<CompletableFuture<ProcessData>> futures = savedItems.stream()
.map(item -> CompletableFuture.supplyAsync(() -> doProcess(item, process), executor))
.collect(Collectors.toList());

sequence(futures).whenComplete((data, throwable) -> {
process.setData(data);
processRepository.save(process); // <-- transaction lost?
log.debug("Process DONE"); // <-- never reached
});
}


Sequence method



 private static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream().map(CompletableFuture::join).collect(Collectors.toList())
);
}


What is happening? Why is the persist call not passing. Is the thread that started the transaction not able to commit the transaction or where does it get lost? All the processed data returns fine and is all good. I've tried different transaction strategies, but how is it possible to control which thread is gonna finish the transaction, if it's the case?



Any advice?










share|improve this question

























  • what about sequence method, can you show it?

    – stjepano
    Feb 25 '16 at 13:46











  • It's jus glue code to perform allOf on the list of futures.

    – Vaelyr
    Feb 25 '16 at 13:54






  • 2





    I'm not 100% on this but it looks like the Transactional starts the transaction on method start and commits on method finish, Transactional does not wait for your callbacks to be fired. You could test that with simple thread.

    – stjepano
    Feb 25 '16 at 13:59











  • Well I assume so too. If I would do List<ProcessData> data = sequence(futures).join() and then repository.save(data), the method blocks and commits fine, I cannot block, but still would need a way to perform it in transaction from start to finish.

    – Vaelyr
    Feb 25 '16 at 14:07











  • @stjepano you are right Transactional start transaction at the begining of the execution and commit (or rollback if runtime error) at the end of the method.

    – JEY
    Feb 25 '16 at 14:11














3












3








3








Idea



I have a processing method which takes in a list of items and processes them asynchronously using external web service. The process steps also persist data while processing. At the end of whole process, I want to persist the whole process along with each processed results as well.



Problem



I convert each item in the list into CompletableFuture and run a processing task on them, and put them back into an array of futures. Now using its .ofAll method (in sequence method) to complete future when all the submitted tasks are completed and return another CompletableFuture which holds the result.



When I want to get that result, I call .whenComplete(..), and would want to set the returned result into my entity as data, and then persist to the database, however the repository save call just does nothing and continues threads just continue running, it's not going past the repository save call.



 @Transactional
public void process(List<Item> items) {
List<Item> savedItems = itemRepository.save(items);

final Process process = createNewProcess();

final List<CompletableFuture<ProcessData>> futures = savedItems.stream()
.map(item -> CompletableFuture.supplyAsync(() -> doProcess(item, process), executor))
.collect(Collectors.toList());

sequence(futures).whenComplete((data, throwable) -> {
process.setData(data);
processRepository.save(process); // <-- transaction lost?
log.debug("Process DONE"); // <-- never reached
});
}


Sequence method



 private static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream().map(CompletableFuture::join).collect(Collectors.toList())
);
}


What is happening? Why is the persist call not passing. Is the thread that started the transaction not able to commit the transaction or where does it get lost? All the processed data returns fine and is all good. I've tried different transaction strategies, but how is it possible to control which thread is gonna finish the transaction, if it's the case?



Any advice?










share|improve this question
















Idea



I have a processing method which takes in a list of items and processes them asynchronously using external web service. The process steps also persist data while processing. At the end of whole process, I want to persist the whole process along with each processed results as well.



Problem



I convert each item in the list into CompletableFuture and run a processing task on them, and put them back into an array of futures. Now using its .ofAll method (in sequence method) to complete future when all the submitted tasks are completed and return another CompletableFuture which holds the result.



When I want to get that result, I call .whenComplete(..), and would want to set the returned result into my entity as data, and then persist to the database, however the repository save call just does nothing and continues threads just continue running, it's not going past the repository save call.



 @Transactional
public void process(List<Item> items) {
List<Item> savedItems = itemRepository.save(items);

final Process process = createNewProcess();

final List<CompletableFuture<ProcessData>> futures = savedItems.stream()
.map(item -> CompletableFuture.supplyAsync(() -> doProcess(item, process), executor))
.collect(Collectors.toList());

sequence(futures).whenComplete((data, throwable) -> {
process.setData(data);
processRepository.save(process); // <-- transaction lost?
log.debug("Process DONE"); // <-- never reached
});
}


Sequence method



 private static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream().map(CompletableFuture::join).collect(Collectors.toList())
);
}


What is happening? Why is the persist call not passing. Is the thread that started the transaction not able to commit the transaction or where does it get lost? All the processed data returns fine and is all good. I've tried different transaction strategies, but how is it possible to control which thread is gonna finish the transaction, if it's the case?



Any advice?







java spring multithreading hibernate transactions






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Feb 25 '16 at 13:53







Vaelyr

















asked Feb 25 '16 at 13:32









VaelyrVaelyr

1,56711020




1,56711020













  • what about sequence method, can you show it?

    – stjepano
    Feb 25 '16 at 13:46











  • It's jus glue code to perform allOf on the list of futures.

    – Vaelyr
    Feb 25 '16 at 13:54






  • 2





    I'm not 100% on this but it looks like the Transactional starts the transaction on method start and commits on method finish, Transactional does not wait for your callbacks to be fired. You could test that with simple thread.

    – stjepano
    Feb 25 '16 at 13:59











  • Well I assume so too. If I would do List<ProcessData> data = sequence(futures).join() and then repository.save(data), the method blocks and commits fine, I cannot block, but still would need a way to perform it in transaction from start to finish.

    – Vaelyr
    Feb 25 '16 at 14:07











  • @stjepano you are right Transactional start transaction at the begining of the execution and commit (or rollback if runtime error) at the end of the method.

    – JEY
    Feb 25 '16 at 14:11



















  • what about sequence method, can you show it?

    – stjepano
    Feb 25 '16 at 13:46











  • It's jus glue code to perform allOf on the list of futures.

    – Vaelyr
    Feb 25 '16 at 13:54






  • 2





    I'm not 100% on this but it looks like the Transactional starts the transaction on method start and commits on method finish, Transactional does not wait for your callbacks to be fired. You could test that with simple thread.

    – stjepano
    Feb 25 '16 at 13:59











  • Well I assume so too. If I would do List<ProcessData> data = sequence(futures).join() and then repository.save(data), the method blocks and commits fine, I cannot block, but still would need a way to perform it in transaction from start to finish.

    – Vaelyr
    Feb 25 '16 at 14:07











  • @stjepano you are right Transactional start transaction at the begining of the execution and commit (or rollback if runtime error) at the end of the method.

    – JEY
    Feb 25 '16 at 14:11

















what about sequence method, can you show it?

– stjepano
Feb 25 '16 at 13:46





what about sequence method, can you show it?

– stjepano
Feb 25 '16 at 13:46













It's jus glue code to perform allOf on the list of futures.

– Vaelyr
Feb 25 '16 at 13:54





It's jus glue code to perform allOf on the list of futures.

– Vaelyr
Feb 25 '16 at 13:54




2




2





I'm not 100% on this but it looks like the Transactional starts the transaction on method start and commits on method finish, Transactional does not wait for your callbacks to be fired. You could test that with simple thread.

– stjepano
Feb 25 '16 at 13:59





I'm not 100% on this but it looks like the Transactional starts the transaction on method start and commits on method finish, Transactional does not wait for your callbacks to be fired. You could test that with simple thread.

– stjepano
Feb 25 '16 at 13:59













Well I assume so too. If I would do List<ProcessData> data = sequence(futures).join() and then repository.save(data), the method blocks and commits fine, I cannot block, but still would need a way to perform it in transaction from start to finish.

– Vaelyr
Feb 25 '16 at 14:07





Well I assume so too. If I would do List<ProcessData> data = sequence(futures).join() and then repository.save(data), the method blocks and commits fine, I cannot block, but still would need a way to perform it in transaction from start to finish.

– Vaelyr
Feb 25 '16 at 14:07













@stjepano you are right Transactional start transaction at the begining of the execution and commit (or rollback if runtime error) at the end of the method.

– JEY
Feb 25 '16 at 14:11





@stjepano you are right Transactional start transaction at the begining of the execution and commit (or rollback if runtime error) at the end of the method.

– JEY
Feb 25 '16 at 14:11












2 Answers
2






active

oldest

votes


















5














The reason of your problem is, as said above, that the transaction ends
when the return of method process(..) is reached.



What you can do, is create the transaction manually, that gives you full
control over when it starts and ends.



Remove @Transactional



Autowire the TransactionManager then in process(..) :



    TransactionDefinition txDef = new DefaultTransactionDefinition();
TransactionStatus txStatus = transactionManager.getTransaction(txDef);
try {
//do your stuff here like
doWhateverAsync().then(transactionManager.commit(txStatus);)
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}





share|improve this answer



















  • 2





    Important to note that doWhateverAsync must operate in the same transactional context created. Care should be taken when doing operations in other threads since the same context may not be available.

    – vishr
    Oct 12 '16 at 17:21











  • @vishr just adding on that any work happening in CompletableFuture will it have the transaction even if we are manually closing the the transaction after the completion of the future, because as per my understanding completablefuture would be running on a new thread(or executable thread pool) not the the thread on which our request was being served. And the new thread might not have same context.

    – pannu
    Nov 20 '17 at 7:31











  • How do you autowire the transaction?

    – markthegrea
    Dec 10 '18 at 16:12



















2














In case of Spring Boot Application , you need following configurations.



The main application method should be annotated with @EnableAsync.



@Async annotation should be on the top of method having @Transactional annotation. This is necessary to indicate processing will be taking place in child thread.






share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f35628764%2fcompletablefuture-vs-spring-transactions%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    5














    The reason of your problem is, as said above, that the transaction ends
    when the return of method process(..) is reached.



    What you can do, is create the transaction manually, that gives you full
    control over when it starts and ends.



    Remove @Transactional



    Autowire the TransactionManager then in process(..) :



        TransactionDefinition txDef = new DefaultTransactionDefinition();
    TransactionStatus txStatus = transactionManager.getTransaction(txDef);
    try {
    //do your stuff here like
    doWhateverAsync().then(transactionManager.commit(txStatus);)
    } catch (Exception e) {
    transactionManager.rollback(txStatus);
    throw e;
    }





    share|improve this answer



















    • 2





      Important to note that doWhateverAsync must operate in the same transactional context created. Care should be taken when doing operations in other threads since the same context may not be available.

      – vishr
      Oct 12 '16 at 17:21











    • @vishr just adding on that any work happening in CompletableFuture will it have the transaction even if we are manually closing the the transaction after the completion of the future, because as per my understanding completablefuture would be running on a new thread(or executable thread pool) not the the thread on which our request was being served. And the new thread might not have same context.

      – pannu
      Nov 20 '17 at 7:31











    • How do you autowire the transaction?

      – markthegrea
      Dec 10 '18 at 16:12
















    5














    The reason of your problem is, as said above, that the transaction ends
    when the return of method process(..) is reached.



    What you can do, is create the transaction manually, that gives you full
    control over when it starts and ends.



    Remove @Transactional



    Autowire the TransactionManager then in process(..) :



        TransactionDefinition txDef = new DefaultTransactionDefinition();
    TransactionStatus txStatus = transactionManager.getTransaction(txDef);
    try {
    //do your stuff here like
    doWhateverAsync().then(transactionManager.commit(txStatus);)
    } catch (Exception e) {
    transactionManager.rollback(txStatus);
    throw e;
    }





    share|improve this answer



















    • 2





      Important to note that doWhateverAsync must operate in the same transactional context created. Care should be taken when doing operations in other threads since the same context may not be available.

      – vishr
      Oct 12 '16 at 17:21











    • @vishr just adding on that any work happening in CompletableFuture will it have the transaction even if we are manually closing the the transaction after the completion of the future, because as per my understanding completablefuture would be running on a new thread(or executable thread pool) not the the thread on which our request was being served. And the new thread might not have same context.

      – pannu
      Nov 20 '17 at 7:31











    • How do you autowire the transaction?

      – markthegrea
      Dec 10 '18 at 16:12














    5












    5








    5







    The reason of your problem is, as said above, that the transaction ends
    when the return of method process(..) is reached.



    What you can do, is create the transaction manually, that gives you full
    control over when it starts and ends.



    Remove @Transactional



    Autowire the TransactionManager then in process(..) :



        TransactionDefinition txDef = new DefaultTransactionDefinition();
    TransactionStatus txStatus = transactionManager.getTransaction(txDef);
    try {
    //do your stuff here like
    doWhateverAsync().then(transactionManager.commit(txStatus);)
    } catch (Exception e) {
    transactionManager.rollback(txStatus);
    throw e;
    }





    share|improve this answer













    The reason of your problem is, as said above, that the transaction ends
    when the return of method process(..) is reached.



    What you can do, is create the transaction manually, that gives you full
    control over when it starts and ends.



    Remove @Transactional



    Autowire the TransactionManager then in process(..) :



        TransactionDefinition txDef = new DefaultTransactionDefinition();
    TransactionStatus txStatus = transactionManager.getTransaction(txDef);
    try {
    //do your stuff here like
    doWhateverAsync().then(transactionManager.commit(txStatus);)
    } catch (Exception e) {
    transactionManager.rollback(txStatus);
    throw e;
    }






    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Feb 25 '16 at 20:18









    Stefan Isele - prefabware.comStefan Isele - prefabware.com

    4,95711619




    4,95711619








    • 2





      Important to note that doWhateverAsync must operate in the same transactional context created. Care should be taken when doing operations in other threads since the same context may not be available.

      – vishr
      Oct 12 '16 at 17:21











    • @vishr just adding on that any work happening in CompletableFuture will it have the transaction even if we are manually closing the the transaction after the completion of the future, because as per my understanding completablefuture would be running on a new thread(or executable thread pool) not the the thread on which our request was being served. And the new thread might not have same context.

      – pannu
      Nov 20 '17 at 7:31











    • How do you autowire the transaction?

      – markthegrea
      Dec 10 '18 at 16:12














    • 2





      Important to note that doWhateverAsync must operate in the same transactional context created. Care should be taken when doing operations in other threads since the same context may not be available.

      – vishr
      Oct 12 '16 at 17:21











    • @vishr just adding on that any work happening in CompletableFuture will it have the transaction even if we are manually closing the the transaction after the completion of the future, because as per my understanding completablefuture would be running on a new thread(or executable thread pool) not the the thread on which our request was being served. And the new thread might not have same context.

      – pannu
      Nov 20 '17 at 7:31











    • How do you autowire the transaction?

      – markthegrea
      Dec 10 '18 at 16:12








    2




    2





    Important to note that doWhateverAsync must operate in the same transactional context created. Care should be taken when doing operations in other threads since the same context may not be available.

    – vishr
    Oct 12 '16 at 17:21





    Important to note that doWhateverAsync must operate in the same transactional context created. Care should be taken when doing operations in other threads since the same context may not be available.

    – vishr
    Oct 12 '16 at 17:21













    @vishr just adding on that any work happening in CompletableFuture will it have the transaction even if we are manually closing the the transaction after the completion of the future, because as per my understanding completablefuture would be running on a new thread(or executable thread pool) not the the thread on which our request was being served. And the new thread might not have same context.

    – pannu
    Nov 20 '17 at 7:31





    @vishr just adding on that any work happening in CompletableFuture will it have the transaction even if we are manually closing the the transaction after the completion of the future, because as per my understanding completablefuture would be running on a new thread(or executable thread pool) not the the thread on which our request was being served. And the new thread might not have same context.

    – pannu
    Nov 20 '17 at 7:31













    How do you autowire the transaction?

    – markthegrea
    Dec 10 '18 at 16:12





    How do you autowire the transaction?

    – markthegrea
    Dec 10 '18 at 16:12













    2














    In case of Spring Boot Application , you need following configurations.



    The main application method should be annotated with @EnableAsync.



    @Async annotation should be on the top of method having @Transactional annotation. This is necessary to indicate processing will be taking place in child thread.






    share|improve this answer




























      2














      In case of Spring Boot Application , you need following configurations.



      The main application method should be annotated with @EnableAsync.



      @Async annotation should be on the top of method having @Transactional annotation. This is necessary to indicate processing will be taking place in child thread.






      share|improve this answer


























        2












        2








        2







        In case of Spring Boot Application , you need following configurations.



        The main application method should be annotated with @EnableAsync.



        @Async annotation should be on the top of method having @Transactional annotation. This is necessary to indicate processing will be taking place in child thread.






        share|improve this answer













        In case of Spring Boot Application , you need following configurations.



        The main application method should be annotated with @EnableAsync.



        @Async annotation should be on the top of method having @Transactional annotation. This is necessary to indicate processing will be taking place in child thread.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Jan 31 '18 at 5:21









        GyroGyro

        413310




        413310






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f35628764%2fcompletablefuture-vs-spring-transactions%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            這個網誌中的熱門文章

            Tangent Lines Diagram Along Smooth Curve

            Yusuf al-Mu'taman ibn Hud

            Zucchini