4

Within my application I have several @Transactional annotations on different methods.

They use the Crud Repository to carry out deletes on my database. I am wondering at what stage do these deletes get carried out, and do I need all the @Transactional annotations?

For example, 2 delete methods that are later called in a façade method, the façade method is then called in the runner:

1. method 1:

@Transactional
public void deleteFromSchemaOne(){

}

1. method 2:

@Transactional
public void deleteFromSchemaTwo(){

}

2. Calling these 2 methods in a façade method:

@Transacational
public void deleteFromAllSchemas(){

deleteFromSchemaOne();
deleteFromSchemaTwo();

}

3. Calling the façade method in the runner

@Transactional
    public void run(String... args) throws Exception {

        service.deleteFromAllSchemas();


}

What I would like to know is:

What stage will my deletes actually be carried out? Will it be when the method is completed within the Runner?

EDIT:

Façade method with loop:

public void deleteFromAllSchemas() {

    for(int i = 0; i < list.size(); i++){

     deleteFromSchemaOne();
     deleteFromSchemaTwo();

    }

}

java123999
  • 6,974
  • 36
  • 77
  • 121

1 Answers1

4

When the outermost transaction finishes, so after run(). Actually it's not the outermost, it's the only transaction. The inner @Transactionals don't create a new transaction, they just join the existing one.

If you need to make sure that some changes are committed even before the outer transaction finishes, you need to set the propagation of the transaction with @Transactional(propagation=Propagation.REQUIRES_NEW). This will create a new transaction even if there is one that already exists.

In your example code (at least based on what it contains now), the run() method probably shouldn't even start a transaction.

If I understood you correctly, you want something along the lines of

public void run() {
    for(int i = 0;i < list.size(); i++)
        service.deleteFromAllSchemas(i);
}

@Transactional
public void deleteFromAllSchemas(int i) {
    deleteFromSchemaOne(i);
    deleteFromSchemaTwo(i);
}
Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • Basically what I want to do is to make sure that the deleteFromAllSchemas() method is transactional- i.e. everything in schema one and two must be deleted or nothing at all, how can I do so? Thanks for your help. – java123999 May 18 '16 at 10:37
  • 1
    That's accomplished with the `@Transactional` in your `deleteFromAllSchemas()`. If an error occurs, it will roll back any changes done in `deleteFromSchemaOne()` and `deleteFromSchemaTwo()`. You should really read up on transactions too. They're not something you can just hope to get right or "learn on the job". – Kayaman May 18 '16 at 10:39
  • 1
    This is correct. I would like to add that in general, transactions should be as short as possible. So unless these database deletes depend on each other (let's say if one fails, you would want to rollback the whole transaction), you should annotate only the two delete methods. That ensures that that each delete will run in a separate transaction. If they do depend on each other. Annotate the method that invokes them and optionally annotate the delete methods with `@Transactional(propagation=Propagation.MANDATORY)` – tkralik May 18 '16 at 10:40
  • Thanks, so in what case would I need the @Transactional annotation on the runner? – java123999 May 18 '16 at 10:42
  • @java123999 That's too broad to discuss. Maybe never, maybe in some cases depending on your design. – Kayaman May 18 '16 at 10:44
  • ok but current the @Transactional on the runner is batching all the statements from every time the façade method is run, then executing them? – java123999 May 18 '16 at 10:52
  • You really don't have control over when your JPA implementation actually executes the queries. But the transaction starts when your facade method is invoked and is commited/rollbacked when the method returns. But why would you want a transaction to start in a facade method? Seems like an antipattern to me – tkralik May 18 '16 at 10:55
  • Because I want to ensure that everything is deleted from the both schemas or rolled back? – java123999 May 18 '16 at 11:01
  • @TomasKralik There's nothing antipatterny about it. A service method will usually have `@Transactional` to make sure that the service can be called by itself, but in facade you may want to perform several service calls as a whole. In those cases you'd start the transaction on the facade level. – Kayaman May 18 '16 at 11:07
  • Right, I mistook the run method for the facade method. In that case, refer to the second part of my previous comment – tkralik May 18 '16 at 11:07
  • OK im now confused, do I need the @transactional annotation on the run method or not? – java123999 May 18 '16 at 11:13
  • @java123999 Not if you're not doing anything else in run. Or if you're doing things before or after the facade call that aren't involved with the database. Seriously, read up on transactions. You don't want to just *think* you understand how they work. – Kayaman May 18 '16 at 11:20
  • Thank you, can you please see my edit, where would I place the @transactional annotation if I wanted to do a commit after every iteration of the loop? – java123999 May 18 '16 at 11:24
  • You can't do it with that code. You'd need a method that calls both delete methods, have that as `@Transactional`, and call that method in a loop. And no `@Transactional` anywhere else (except the delete methods if you want to). – Kayaman May 18 '16 at 11:26
  • @java123999 Remember though that you can't have both the methods in the same class, since if you call `deleteInLoop()` from outside, it will be handled by Spring, but if you call `deleteBoth()` from `deleteInLoop()` Spring won't get to intercept the method call to start the transaction. I.e. do the looping in your `run()` for example. – Kayaman May 18 '16 at 11:34
  • @Kayaman can you please show what you mean in code example, I am not sure I understand. I appreciate the help – java123999 May 18 '16 at 11:59