I wanted to know how the Spring #Transactional will work for the following Coding scenarios. I am using Spring 4 + Hiberante 5 with Oracle 19C database for this example.
Example 1:
#Service
public class UserService {
#Transactional(readOnly = true)
public void invoice() {
createPdf();
// send invoice as email, etc.
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPdf() {
// ...
}
}
Example 2:
#Service
public class UserService {
#Autowired
private InvoiceService invoiceService;
#Transactional(readOnly = true)
public void invoice() {
invoiceService.createPdf();
// send invoice as email, etc.
}
}
#Service
public class InvoiceService {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPdf() {
// ...
}
}
Thanks
Example 1: As you are calling the createPDF method from inside your Service, the #Transactional(REQUIRES_NEW) annotation will effectively be ignored. There will be no new transaction opened.
Example 2: As your are calling another service, which is wrapped in a transactional proxy, you will get a new transaction, as the annotation is respected.
You also might want to read up on this article: Spring Transaction Management: #Transactional In-Depth
Related
I know there are a lot of similar issues here on stackoverflow, but none of this fixes my issue.
I want to expose some methods from my Spring Boot Repositories as Webservice, but one Repository randomly ^(1) only returns 404 on all Methods.
We are talking about following classes
#Component
public class CustomRepositoryRestConfigurer implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.disableDefaultExposure();
}
}
#RepositoryRestResource(exported = true)
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
#Query("select t from Transaction where ....")
#RestResource(exported = true) // true is default
Page<Transaction> findAllByQuery(
// more stuff
#Param("text") String text,
Pageable pageable);
void delete(Transaction entity); // should not be exposed!
}
And following tests will fail:
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = Application.class)
public class SampleTest {
#Autowired
private MockMvc mvc;
#Test
public void check_profile_is_ok() throws Exception {
mvc.perform(get("/")
// fails...
.andExpect(jsonPath("$._links.transactions").value(notNullValue()))
}
#Test
public void check_access() throws Exception {
mvc.perform(get("/transactions/search/findAllByQuery")
// fails...
.andExpect(status().isOk());
}
}
When remove config.disableDefaultExposure(); the Testcase will pass, but then all Endpoints are exposed - and I don't want this.
Is there any configuration I'm missing?
I have a second repository CategoryRepository and everything is same but working.
Okay I solved it by myself. I had (falsely) a second Repository on the Transaction Entity.
I am trying to use the #Async capabilities of the Spring framework to perform a simple indexing task.
The problem I'm facing is that I feel that the EntityManager used in my Async function is somehow reused from previous calls so my data is not up to date and sometimes uses old data.
Here is the code I wrote as an example. The goal is to update a product's data and index it asynchronously after I publish an event using Spring's ApplicationEventPublisher:
ProductService
#Service
class ProductService {
private final EntityManager entityManager;
private final ApplicationEventPublisher eventPublisher;
#Autowired
public ProductService(EntityManager entityManager, ApplicationEventPublisher eventPublisher) {
this.entityManager = entityManager;
this.eventPublisher = eventPublisher;
}
#Transactional
public void patchProduct (String id, ProductDto productDto) {
Product product = this.entityManager.find(Product.class, id);
product.setLabel(productDto.getLabel());
this.entityManager.flush();
this.eventPublisher.publishEvent(new ProductEvent(product, ProductEvent.EVENT_TYPE.UPDATED));
}
}
EventListener
#Component
public class ProductEventListener {
private final AsyncProcesses asyncProcesses;
#Autowired
public ProductEventListener (
AsyncProcesses asyncProcesses
) {
this.asyncProcesses = asyncProcesses;
}
#EventListener
public void indexProduct (ProductEvent productEvent) {
this.asyncProcesses.indexProduct(productEvent.getProduct().getPok());
}
}
AsyncProcesses
#Service
public class AsyncProcesses {
private final SlowProcesses slowProcesses;
#Autowired
public AsyncProcesses(SlowProcesses slowProcesses) {
this.slowProcesses = slowProcesses;
}
#Async
public void indexProduct (String id) {
this.slowProcesses.indexProduct(id);
}
}
SlowProcesses
#Service
public class SlowProcesses {
private EntityManager entityManager;
private ProductSearchService productSearchService;
#Autowired
public SlowProcesses(EntityManager entityManager, NewProductSearchService newProductSearchService) {
this.entityManager = entityManager;
this.newProductSearchService = newProductSearchService;
}
#Transactional(readonly = true)
public void indexProduct (String pok) {
Product product = this.entityManager.find(Product.class, pok);
// this.entityManager.refresh(product); -> If I uncomment this line, everything works as expected
this.productSearchService.indexProduct(product);
}
}
As you can see on the SlowProcesses file, if I refresh the product object in the entityManager, I get the correct and up to date data. If I do not, I might get old data from previous calls.
What is the correct way to use the EntityManager in an Asynchronous call? Do I really have to refresh all my objects in order to make everything work? Am I doing something else wrong?
Thank you for reading through
Since instances of EntityManager are not thread-safe as pointed out by Jordie, you may want to try this instead:
Instead of injecting an EntityManager, inject an EntityManagerFactory. Then from the EntityManagerFactory retrieve a new EntityManager instance that is used only for the duration of the method in question.
I would like to test method which have to create 2 transactions. But it seems that I can not access the session in scope of second transaction (another service method with #Transactional(propagation = Propagation.REQUIRES_NEW)). This issue happens only in tests. The code example:
#Service
public class ServiceA implements A {
#Autowired
private ServiceB serviceB;
#Transactional
public void m1() {
//some actions with repositories
serviceB.m2(id);
}
}
#Service
public class ServiceB implements B {
#Autowired
private RepositoryA repositoryA;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void m2(Long id) {
//some other actions
m3(repositoryA.findOne(id).getSomething()); // NPE is thrown here
//some other actions
}
}
I get NullPointerException but only during tests (I am sure that object with such id exists in my test databese). When I try to get the same object in scope of first transaction, everything is working as expected
public class ServiceATest extends BaseTest {
#Test
public void test() {
mockMvc.perform(get("/PATH_TO_A/" + ID).session(createSession(user)))
.andExpect(status().isOk());
}
}
I get 404 HTTP code instead of 200 on reason of NullPointerException.
How to handle such case with multiple transactions during testing via JUnit?
I'm using Spring Boot with Solr.
I can see Spring-tx in my resolved dependencies.
When I call a #Transactional annotated method in a Spring bean,
a) at runtime, i don't see any signs of it being wrapped in any transaction management proxy, and
b) when the method throws a RuntimeException the data is not rolledback.
The email/phone repositories and just interfaces that extend
org.springframework.data.solr.repository.SolrCrudRepository
What am i missing?
At have the method annotated #Transactional in both the interface and the implementation, just in case ;-)
public interface MyServiceInterface {
#Transactional
public CreateDetailsResponse createAllDetails(
final CreateDetailsRequest createDetailsRequest) throws BusinessException;
}
public class MyService implements MyServiceInterface {
#Autowired
private EmailRepository emailRepository;
#Autowired
private EmailRepository phoneRepository;
#Transactional
public CreateDetailsResponse createAllDetails(
final CreateDetailsRequest createDetailsRequest) throws BusinessException {
//method below calls emailRepository.save(emailDocument)
saveEmail();
//method below is supposed to call phoneRepository.save(phoneDocument)
//but throws RuntimeException before save is called
savePhone();
}
//...
}
#Configuration
#EnableSolrRepositories(basePackages = "com.....repository", multicoreSupport = true,
considerNestedRepositories = true, repositoryBaseClass = SoftCommitSimpleSolrRepository.class)
#EnableAutoConfiguration
public class ConsumersServiceConfiguration {
//...
#Bean
public MyServiceInterface myService() {
return new MyService();
}
}
Supposedly Solr supports transactions:
I am learning to use JPA. And I'm a little confused.
According JPA EntityManager manages transactions. But a design pattern is to inject the EntityManager in DAOs. So how is possible that are different EntityManager to the same transaction?
This is the case I want to solve
I have the DAOs defined
#Repository
JPARepository1 {
#PersistenceContext
protected EntityManager em;
....
.
#Repository
JPARepository2 {
#PersistenceContext
protected EntityManager em;
....
I have a Service
#Service
public class ServiceImpl1 {
#Autowired
private JPARepository1 repo1;
#Autowired
private JPARepository2 repo2;
public void mainMethod(){
Object o= transactionalMethod1();
try{
transactionalMethod2(o);
}catch (Exception e){
transactionalMethod3(o);
}
}
private Object transactionalMethod1(){
....
}
private void transactionalMethod2(Object o){
....
}
private void transactionalMethod3(Object o){
....
}
Then from #Controller I will invoke mainMethod().
What would be the right way to do transactional to transactionalMethod1, transactionalMethod2 and transactionalMethod3,within the same Service and using the same Repository's.
I would like it if there is an exeption in transactionalMethod2, this abort the transaction, but keep the transactions of transactionalMethod1 and transactionalMethod3
Thanks, sorry for my English
Usually you configure one EntityManager, so the wired manager is always the same, the one you configured. The instance of this manager though, is different in every wiring.
So, every transaction in your service uses a different instance of the EntityManager and thus every transaction invoked is seperated from each other.
As so, an exception in transactionalMethod2 doesn't necessarily affects the transactionalMethod1 and transactionalMethod3
What would be the right way to do transactional to transactionalMethod1, transactionalMethod2 and transactionalMethod3,within the same Service and using the same Repository's.
Now, you have two options to do service methods transactions
1) You could annotate your whole #Service like that:
#Service
#Transactional
public class ServiceImpl1 {
....
so every method declared here is also a transaction.
2) You could annotate each method as #Transactional:
#Transactional
private Object transactionalMethod1(){
....
}
#Transactional
private void transactionalMethod2(Object o){
....
}
#Transactional
private void transactionalMethod3(Object o){
....
}
If you want to use a single repository just #Autowired a single one and use it in your #Transactional method. E.g:
#Service
#Transactional
public class ServiceImpl1 {
#Autowired
private JPARepository1 repo1;
public void mainMethod(){
Object o= transactionalMethod1();
try{
transactionalMethod2(o);
}catch (Exception e){
transactionalMethod3(o);
}
}
private Object transactionalMethod1(){
return repo1.findOne();
}
private void transactionalMethod2(Object o){
repo1.create(o);
}
private void transactionalMethod3(Object o){
repo1.delete(o)
}