CompletableFuture vs Spring Transactions
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
|
show 1 more comment
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
what aboutsequence
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 theTransactional
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 doList<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
|
show 1 more comment
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
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
java spring multithreading hibernate transactions
edited Feb 25 '16 at 13:53
Vaelyr
asked Feb 25 '16 at 13:32
VaelyrVaelyr
1,56711020
1,56711020
what aboutsequence
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 theTransactional
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 doList<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
|
show 1 more comment
what aboutsequence
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 theTransactional
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 doList<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
|
show 1 more comment
2 Answers
2
active
oldest
votes
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;
}
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
add a comment |
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.
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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;
}
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
add a comment |
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;
}
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
add a comment |
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;
}
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;
}
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
add a comment |
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
add a comment |
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.
add a comment |
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.
add a comment |
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.
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.
answered Jan 31 '18 at 5:21
GyroGyro
413310
413310
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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