105

I just started working on a Spring-data, Hibernate, MySQL, JPA project. I switched to spring-data so that I wouldn't have to worry about creating queries by hand.

I noticed that the use of @Transactional isn't required when you're using spring-data since I also tried my queries without the annotation.

Is there a specific reason why I should/shouldn't be using the @Transactional annotation?

Works:

@Transactional
public List listStudentsBySchool(long id) {
    return repository.findByClasses_School_Id(id);
}

Also works:

public List listStudentsBySchool(long id) {
    return repository.findByClasses_School_Id(id);
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
Byron Voorbach
  • 4,365
  • 5
  • 27
  • 35

5 Answers5

168

What is your question actually about? The usage of the @Repository annotation or @Transactional.

@Repository is not needed at all as the interface you declare will be backed by a proxy the Spring Data infrastructure creates and activates exception translation for anyway. So using this annotation on a Spring Data repository interface does not have any effect at all.

@Transactional - for the JPA module we have this annotation on the implementation class backing the proxy (SimpleJpaRepository). This is for two reasons: first, persisting and deleting objects requires a transaction in JPA. Thus we need to make sure a transaction is running, which we do by having the method annotated with @Transactional.

Reading methods like findAll() and findOne(…) are using @Transactional(readOnly = true) which is not strictly necessary but triggers a few optimizations in the transaction infrastructure (setting the FlushMode to MANUAL to let persistence providers potentially skip dirty checks when closing the EntityManager). Beyond that the flag is set on the JDBC Connection as well which causes further optimizations on that level.

Depending on what database you use it can omit table locks or even reject write operations you might trigger accidentally. Thus we recommend using @Transactional(readOnly = true) for query methods as well which you can easily achieve adding that annotation to you repository interface. Make sure you add a plain @Transactional to the manipulating methods you might have declared or re-decorated in that interface.

Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
  • 13
    So in short: I should use @Transactional on add/edit/delete queries and @Transaction(readOnly = true) on select queries on all my DAO-methods? – Byron Voorbach May 05 '12 at 23:29
  • 27
    Exactly. The easiest way to do so is by using `@Transactional(readOnly = true)` on the interface (as it usually contains mostly finder methods) and override this setting for each modifying query method with a plain `@Transactional`. That's actually the way it's done in `SimpleJpaRepositoy`. – Oliver Drotbohm May 06 '12 at 10:07
  • @Oliver thanks for the comprehensive explanation..But while going through other link [transaction-pit-falls] . It says **"_The bottom line is that when you use an ORM-based framework, the read-only flag is quite useless and in most cases is ignored. But if you still insist on using it, always set the propagation mode to SUPPORTS_"**..After reading this I am not sure if I should use (readOnly = true) alone.. should it always be used with propagation mode as SUPPORTS. – Anupam Gupta May 05 '13 at 08:19
  • 10
    Close to everything is wrong in this section of the article. By indicating that you're not writing, the JDBC driven can (will) improve performance for DB interactions. It also can detect and reject accidentally issued writes as well. On top of that, Spring disables JPA/Hibernate flushing in read only mode which can hugely affect performance in case you read large object graphs as the provider doesn't need to perform dirty checks on it then. The flag might not have big impact on the transaction itself though, but that by far not everything to consider. – Oliver Drotbohm May 07 '13 at 12:44
  • I can vouch for the performance improvements in case of large object graphs or use cases loading large number of managed objects. – Shailendra Feb 16 '15 at 14:17
  • Didn't read the article but one must ask why use a transaction at all if all I plan on doing is a SELECT? I think this is probably what the linked article tries to describe. There is no such point. Propagation "SUPPORTS" will use the current running transaction if such exists, otherwise not create one. That's key. Only persist and delete operations requires a transaction. So WHAT IS the point of having a "read-only transaction" one must ask? Better to not use one at all. – Martin Andersson Jul 14 '19 at 16:21
  • 1
    @MartinAndersson note, that any speak to database is running in transaction. Have a look here for nice explanation and something more to learn around: https://stackoverflow.com/questions/13539213/why-do-i-need-transaction-in-hibernate-for-read-only-operations – Lubo Feb 09 '20 at 14:39
  • To read more about some optimizations in hibernate about readOnly = true thing, you can have a look at https://vladmihalcea.com/spring-read-only-transaction-hibernate-optimization/ – Lubo Feb 09 '20 at 14:44
  • 2
    btw, don't forget to use `@EnableTransactionManagement` on any of your `@Configuration` classes, in order for `@Transactional` to work properly – hello_earth Oct 04 '21 at 12:16
3

In your examples it depends on if your repository has @Transactional or not.

If yes, then service, (as it is) in your case - should no use @Transactional (since there is no point using it). You may add @Transactional later if you plan to add more logic to your service that deals with another tables / repositories - then there will be a point having it.

If no - then your service should use @Transactional if you want to make sure you do not have issues with isolation, that you are not reading something that is not yet commuted for example.

--

If talking about repositories in general (as crud collection interface):

  1. I would say: NO, you should not use @Transactional

Why not: if we believe that repository is outside of business context, and it should does not know about propagation or isolation (level of lock). It can not guess in which transaction context it could be involved into.

repositories are "business-less" (if you believe so)

say, you have a repository:

class MyRepository
   void add(entity) {...}
   void findByName(name) {...}

and there is a business logic, say MyService

 class MyService() {

   @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.SERIALIZABLE)
   void doIt() {
      var entity = myRepository.findByName("some-name");
      if(record.field.equal("expected")) {
        ... 
        myRepository.add(newEntity)
      }
   }

 }

I.e. in this case: MyService decides what it wants to involve repository into.

In this cases with propagation="Required" will make sure that BOTH repository methods -findByName() and add() will be involved in single transaction, and isolation="Serializable" would make sure that nobody can interfere with that. It will keep a lock for that table(s) where get() & add() is involved into.

But some other Service may want to use MyRepository differently, not involving into any transaction at all, say it uses findByName() method, not interested in any restriction to read whatever it can find a this moment.

  1. I would say YES, if you treat your repository as one that returns always valid entity (no dirty reads) etc, (saving users from using it incorrectly). I.e. your repository should take care of isolation problem (concurrency & data consistency), like in example:

we want (repository) to make sure then when we add(newEntity) it would check first that there is entity with such the same name already, if so - insert, all in one locking unit of work. (same what we did on service level above, but not we move this responsibility to the repository)

Say, there could not be 2 tasks with the same name "in-progress" state (business rule)

 class TaskRepository
   @Transactional(propagation=Propagation.REQUIRED, 
   isolation=Isolation.SERIALIZABLE)
   void add(entity) {
      var name = entity.getName()
      var found = this.findFirstByName(name);
      if(found == null || found.getStatus().equal("in-progress")) 
      {
        .. do insert
      }
   }
   @Transactional
   void findFirstByName(name) {...}

2nd is more like DDD style repository.


I guess there is more to cover if:

  class Service {
    @Transactional(isolation=.., propagation=...) // where .. are different from what is defined in taskRepository()
    void doStuff() {
      taskRepository.add(task);
    }
  }
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
ses
  • 13,174
  • 31
  • 123
  • 226
0

You should use @Repository annotation

This is because @Repository is used for translating your unchecked SQL exception to Spring Excpetion and the only exception you should deal is DataAccessException

danny.lesnik
  • 18,479
  • 29
  • 135
  • 200
  • 13
    This is true in general when using Spring, but since Spring Data repositories are already backed by a Spring proxy - using @Repository doesn't make any difference. – Aleksander Blomskøld May 06 '12 at 13:35
0

We also use @Transactional annotation to lock the record so that another thread/request would not change the read.

Zozo
  • 185
  • 1
  • 2
  • 15
0

We use @Transactional annotation when we create/update one more entity at the same time. If the method which has @Transactional throws an exception, the annotation helps to roll back the previous inserts.

menoktaokan
  • 346
  • 3
  • 13