@Transactional etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
@Transactional etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

29 Aralık 2020 Salı

SpringData @Transactional - Rollback

Giriş
1. Rollback işlemi için unchecked bir exception fırlatmak yeterli. 
2. Eğer checked exception kullanmak istersek rollbackFor alanını kullanmak gerekir. Açıklaması şöyle
... by default in spring transactions are rolled back only for runtime exceptions. When a checked exception is thrown from your code and you don’t explicitly tell spring that it should rollback the transaction then it get’s committed.
3. Eğer bazı unchecked exception'lar için rollback olmasın istiyorsak noRollbackFor alanı kullanılır.

4. rollbackFor ve noRollbackFor alanları parametre olarak class alır. Bunların string yani class ismi alan türevleri ise rollbackForClassName ve noRollbackForClassName alanları. 

1. rollbackFor Alanı - Class Parametre
Örnek
Şu kod java.lang.ArithmeticException fırlattığı için otomatik rollback yapar
@Transactional
public Response<Void> updateMedium(UserVO userVO){

  UserDomain userDomain = this.getIfPresent(userVO.getId());
  userDomain.setDeleted(userVO.getDeleted());
  UserDomain saved = userRepository.save(userDomain);
  int a = 2 / 0;

  return Response.success();
}
Şu kod artık exception fırlatıyor. unchecked exception fırlatmadığı için artık otomatik rollback yapmaz.
@Transactional
public Response<Void> updateMedium(UserVO userVO) throws Exception {

  UserDomain userDomain = this.getIfPresent(userVO.getId());
  userDomain.setDeleted(userVO.getDeleted());
  userDomain.setPassword(userVO.getPassword());
  userDomain.setUsername(userVO.getUsername());
  userDomain.setCreateTime(new Date());
  UserDomain saved = userRepository.save(userDomain);
  try {
    int a = 2 / 0;
  } catch (Exception e) {
    throw new Exception();
  }

  return Response.success();
}
Kodu şöyle yapmak gerekir
@Transactional(rollbackFor = Exception.class)
Örnek - unchecked exception
Şu kod rollback yapar, çünkü unchecked exception fırlatıyor
@Transactional
public void rollbacksOnRuntimeException() {
  jdbcTemplate.execute("insert into test_table values('rollbacksOnRuntimeException')");
  throw new RuntimeException("Rollback!");
}
Örnek - checked exception
Şu kod rollback yapmaz çünkü checked exception fırlatıyor ancak rollbackOn alanı tanımlı değil
@Transactional
public void noRollbackOnCheckedException() throws Exception {
  jdbcTemplate.execute("insert into test_table values('noRollbackOnCheckedException')");
  throw new Exception("Simple exception");
}
Örnek
Şu iki kod da düzgün rollback yapar. Birincisi checked exception fırlatıyor ve rollBackOn alanı tanımlı. İkincisi ise rollbackOn tanımlı olsa bile zaten unchecked exception fırlatabilir.
@Transactional(rollbackFor = CustomCheckedException.class)
public void withRollbackOnAndDeclaredException() throws CustomCheckedException {
  jdbcTemplate.execute("insert into test_table
values('withRollbackForAndDeclaredException')"
);
  throw new CustomCheckedException("rollback me");
}

@Transactional(rollbackFor = CustomCheckedException.class)
public void withRollbackOnAndRuntimeException() throws CustomCheckedException {
  jdbcTemplate.execute("insert into test_table
values('withRollbackOnAndRuntimeException')"
);
  throw new RuntimeException("rollback me");
}
2. noRollbackFor Alanı - Class Parametre
Aynı transaction içinde bir yerde exception fırlatılıyorsa normalde transaction rollback edilir. Ancak bu exception'ı biz bilerek göz ardı etmek istersek iki tane temel çözüm var.

1. noRollbackFor kullanmak
2. Exception fırlatan kodu REQUIRES_NEW ile yeni bir transaction içine almak.

Örnek
Elimizde şöyle bir kod olsun.
@Transactional
public void addPeople(String name) {
  personRepository.saveAndFlush(new Person("Jack", "Brown"));
  personRepository.saveAndFlush(new Person("Julia", "Green"));
  String resultName = name;
  try {
    personValidateService.validateName(name);
  }
  catch (IllegalArgumentException e) {
    log.error("name is not allowed. Using default one");
    resultName = "DefaultName";
  }
  personRepository.saveAndFlush(new Person(resultName, "Purple"));
  }
}
validateName() şöyle olsun
@Service
public class PersonValidateService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional
  public void validateName(String name) {
    if (name == null || name.isBlank() || personRepository.existsByFirstName(name)) {
      throw new IllegalArgumentException("name is forbidden");
    }
  }
}
Burada amaç eğer validateName() exception fırlatırsa bile default name ile bir kayıt yaratmak. Ancak bu kayıt yaratılmıyor. Çünkü tüm Spring kodları bir @Transaction ile işaretli olduğu için aynı transaction içinde çalışıyor. Spring her hangi bir yerde exception yakarlarsa geri kalan işlemleri de yapmaz. Bu durumda şöyle yaparız
@Service
public class PersonValidateService {
  @Autowired
  private PersonRepository personRepository;

  @Transactional(noRollbackFor = IllegalArgumentException.class)
  public void validateName(String name) {
    if (name == null || name.isBlank() || personRepository.existsByFirstName(name)) {
      throw new IllegalArgumentException("name is forbidden");
    }
  }
}
Açıklaması şöyle
The default @Transactional propagation is REQUIRED. It means that the new transaction is created if it’s missing. And if it’s present already, the current one is supported. So, the whole request is being executed within a single transaction.

Anyway, there is a caveat. If the RuntimeException throws out of the transactional proxy, Spring marks the current transaction as rollback only. That’s exactly what happened in our case. PersonValidateService.validateName throws IllegalArgumentException. Transactional proxy tracks it and sets on the rollback flag. Later executions during the transaction have no effect because they ought to be rolled back in the end.
Örnek
Şöyle yaparız
@Service
@Transactional(
  isolation = Isolation.READ_COMMITTED, 
  propagation = Propagation.SUPPORTS, 
  readOnly = false, 
  timeout = 30)
public class CarService {
 
  @Autowired
  private CarRepository carRepository;
 
  @Transactional(
    rollbackFor = IllegalArgumentException.class, 
    noRollbackFor = EntityExistsException.class,
    rollbackForClassName = "IllegalArgumentException", 
    noRollbackForClassName = "EntityExistsException")
  public Car save(Car car) {
    return carRepository.save(car);
  }
}

22 Temmuz 2020 Çarşamba

SpringData @Transactional Anotasyonu

Giriş
Şu satırı dahil ederiz. Transaction Türkçe'ye etkileşim olarak tercüme ediliyor.
import org.springframework.transaction.annotation.Transactional;
Alanlar şöyle


Başlamadan önce
1. Spring içinde ismi transactionManager olan bean olmalıdır. Eğer ismi daha farklı bir bean kullanmak istersek şöyle yaparız.
<bean id="myTransactionManager"
...
</bean>

<tx:annotation-driven transaction-manager="myTransactionManager"/>
2. @EnableTransactionManagement anotasyonu kodda olmalıdır.

3. SpringBoot kullanıyorsak @EntityScan anotasyonu kodda olmalıdır.

Proxy Kodları
Transaction'ın başlamasını sağlayan proxy kodu.
Örnek
Elimizde şöyle bir kod olsun.
@Transactional 
public void method1(){
  //do something
  call method2();
  //do something
  ...
  ...
  failed here
}

@Transactional
public void method2(){
  //do something
  save()
}
Eğer method2'yi inject edilmeyen bir bean ile kendimiz çağırırsak transaction başlamaz. Açıklaması şöyle.
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. @PostConstruct
Örnek
Elimizde şöyle bir kod olsun. saveCustomer2() REQUIRES_NEW olarak işaretli olmasına rağmen yeni transaction başlatmaz.
@Repository
public class CustomerDAO {  
    @Transactional(value=TxType.REQUIRED)
    public void saveCustomer() {
        // some DB stuff here...
        saveCustomer2();
    }
    @Transactional(value=TxType.REQUIRES_NEW)
    public void saveCustomer2() {
        // more DB stuff here
    }
}
Multi-thread Kodlar
Transaction bir thread içinde başlamalı ve aynı thread içinde bitmelidir. Açıklaması şöyle.
The Spring API works very well with almost all of the transaction management requirements as long as the transaction is on a single thread. The problem arises when we want to manage a transaction across multiple threads. Spring doesn't support transactions over multiple threads out of the box. Spring doesn't explicitly mention that in the documentation, but you will end up with runtime errors or unexpected results if you try to do so.
Persistence Context
@Transactional içinde Persistence Context kullanılır. Açıklaması şöyle.
One of the key points about @Transactional is that there are two separate concepts to consider, each with it's own scope and life cycle:
- the persistence context
- the database transaction
Persitence Context eşittir DB Transaction gibi düşünülüyor ancak öyle değil. Açıklaması şöyle.
The persistence context is just a synchronizer object that tracks the state of a limited set of Java objects and makes sure that changes on those objects are eventually persisted back into the database.

This is a very different notion than the one of a database transaction. One Entity Manager can be used across several database transactions, and it actually often is.
JUnit
Spring test'lerdeki @Transactional olarak işaretli kodları rollback etmek üzere ayarlanmıştır. Açıklaması şöyle.
By default, the framework will create and roll back a transaction for each test.
Açıklaması şöyle.
If you want a transaction to commit - unusual, but occasionally useful when you want a particular test to populate or modify the database - the TestContext framework can be instructed to cause the transaction to commit instead of roll back via the @TransactionConfiguration and @Rollback annotations.
Tanımlama
Sınıf için şöyle yaparız. Bu durumda sınıfın tüm metodları transactional olur.
@Transactional
@Service
public class FooService {
  ...
}
1. isolation Alanı 

DEFAULT Değeri
Şöyle yaparız.
@Transactional(rollbackFor = DataAccessException.class,
               readOnly = false, timeout = 30,
               propagation = Propagation.SUPPORTS,
               isolation = Isolation.DEFAULT)
public void saveFoo(Foo foo) throws DataAccessException {
  ...
}
REPEATABLE_READ Değeri
Açıklaması şöyle
REPEATABLE READ, as the name says, only provides guarantees with respect to existing rows (namely that if a row is found to exist, it won't be altered by other transactions and the read then becomes "repeatable").
Şöyle yaparız.
@Transactional(isolationLevel = REPEATABLE_READ)
void addHuman(int height){
  ...
}
2. label Alanı
Açıklama yaz

3. noRollbackFor Alanı
@Transactional - Rollback yazısına taşıdım.

4. noRollbackForClassName Alanı
Açıklama yaz

5. rollbackFor Alanı
@Transactional - Rollback yazısına taşıdım.

5. rollbackForClassName Alanı
@Transactional - Rollback yazısına taşıdım.


6. propagation Alanı - Mandatory
@Transactional Anotasyonu Propagation Değerleri yazına taşıdım.

7. readOnly Alanı
Örnek
Şöyle yaparız.
@Transactional(readOnly = true)
Örnek
Şöyle yaparız.
@Transactional(readOnly = false, rollbackFor=Exception.class)

8. timeout Alanı
Açıklaması şöyle.
Timeout enables client to control how long the transaction runs before timing out and being rolled back automatically by the underlying transaction infrastructure.
Eğer bir transaction'ın önemliyse bu alanı kullanmak gerekebilir. Açıklaması şöyle.
- One is to stop records being locked for long and unable to serve any other requests.

- Let says you are booking a ticket. On the final submission page, it is talking so long and will your user wait forever? So you set http client time out. But now you have the http client time out, what happens if you don't have transaction time out? You displayed error to user saying it didn't succeed but your transaction takes it time as it does not have any timeout and commits after the your http client has timed out.
9. timeoutString Alanı
Açıklama yaz

10. transactionManager Alanı

Hangi transactionManager bean'inin kullanılacağını beliritr. Normalde bu alanı tanımlamak zorunda değiliz. Tanımlı değilse Spring otomatik olarak transactionManager isimli bean'i kullanır.

transactionManager Tanımlama - jndi DataSource
Şöyle yaparız.
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource"/>
<bean id="transactionManager" 
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
transactionManager Tanımlama - JPA
JpaTransactionManager Sınıfı yazısına taşıdım

transactionManager Tanımlama - HibernateTransactionManager
HibernateTransactionManager Sınıfı yazısına taşıdım

transactionManager Tanımlama - DataSourceTransactionManager
Şöyle yaparız.
<bean id="transactionManager"
   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
11. value Alanı
transactionManager alanı için alias'tır.