Transactional

When annotating a method with @Transactional, we declare that all the database execution within that method will be under a Database transaction.

For example

@Service
public class AuthorService {     
    private AuthorRepository authorRepository;     
     
    public AuthorService(AuthorRepository authorRepository) {               
        this.authorRepository = authorRepository;     
    }     
     
    @Transactional    
    public void updateAuthorNameTransaction() {         
        Author author = authorRepository.findById(1L).get(); 
        author.setName("new name");     
    } 
}

The @Transactional saying that this method in here will be executed under a transaction. So everything has to go through or nothing will go through.

Transaction Propagation

Transaction propagation is for you to handle the use and creation of transactions

We can define the transaction propagation using the following

@Service
public class AuthorService {
 
    private AuthorRepository authorRepository;
 
    public AuthorService(AuthorRepository authorRepository) {
        this.authorRepository = authorRepository;
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateAuthorNameTransaction() {
        Author author = authorRepository.findById(1L).get();
        author.setName("new name");
    }
}

We accept the following options:

  • REQUIRED (default): either join an active transaction or join a new one if needed. It always need transaction
  • SUPPORTS: if there is an active transaction then join. Otherwise just execute without a transactional context
  • MANDATORY: join an active transaction if one exist or throw Exception if there is none active transaction
  • NEVER: should not be in a transaction context. If it's insidde one then throw an exception
  • NOT_SUPPORTED: if an active transaction exist, Spring will pause it and execute this one in non-transactional context
  • REQUIRES_NEW: always start a new transaction for this method. If the method gets called with an active transaction, that transaction gets suspended until this method got executed.
  • NESTED: start a new transaction if the method gets called without an active transaction. If it gets called with an active transaction, Spring sets a savepoint and rolls back to that savepoint if an Exception occurs.

Read Only

Only allow this transaction to read

@Service
public class AuthorService {
 
    private AuthorRepository authorRepository;
 
    public AuthorService(AuthorRepository authorRepository) {
        this.authorRepository = authorRepository;
    }
 
    @Transactional(readOnly = true)
    public Author getAuthor() {
        return authorRepository.findById(1L).get();
    }
}

Rollback

We can rollback the transaction if there is something failed. Also we can declare not to rollback for a certain exceptions

@Service
public class AuthorService {
 
    private AuthorRepository authorRepository;
 
    public AuthorService(AuthorRepository authorRepository) {
        this.authorRepository = authorRepository;
    }
 
    @Transactional(
	    rollbackFor = Exception.class, 
        noRollbackFor = EntityNotFoundException.class
	)
    public void updateAuthorName() {
        Author author = authorRepository.findById(1L).get();
        author.setName("new name");
    }
}

Isolation

To control the I (isolation) in ACID

0. DEFAULT

By default, spring will use the database defined isolation

1. READ_UNCOMMITTED

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
    // ...
}

This only read the uncommited data from other concurrent transaction. Therefore we can get a different result on re-read.

Note: Postgres and Oracle does not support this operation. For Postgres it will automatically falls back to READ_COMMITTED instead.

2. READ_COMMITTED

@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
    // ...
}

Prevent dirty read (the uncommitted) read. However, if some other concurrent transaction changed the result, our read could be changing.

Note: default for Postgres, Oracle and SQL server

3. REPEATABLE_READ

@Transactional(isolation = Isolation.REPEATABLE_READ) 
public void log(String message){
    // ...
}

Prevent dirty, it guarantee that the read data cannot be changed. However new rows can still be added, but the read row are not allowed to be modified.

Note: default for MySQL, Oracle does not support REPEATABLE_READ

4. SERIALIZABLE

@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
    // ...
}

Prevent dirty, guarantee that the read data cannot be changed and new rows cannot be added for the concurrent transaction. This is blocking all the concurrent transaction.