3

I'm making a very simple example to test Spring isolation. I have 2 very similar Schedule classes:

@Service("manager1")
public class Manager1 {

    private Service1 service1;

    @Scheduled(fixedDelay = 15000)
    public void sendScheduledCampaigns() {
        service1.changeCredits();
    }
...

@Service("manager2")
public class Manager2 {

    private Service2 service2;

    @Scheduled(fixedDelay = 15000)
    public void sendScheduledCampaigns() {
        service2.changeCredits();
    }
...

Also, I have 2 very similar service classes:

@Service("service1")
@Transactional
public class Service1 {

    private UserService userService;

    private static Logger log = Logger.getLogger(Service1.class);

    public void changeCredits() {       
        User user = userService.getUserById(1);

        log.info("Service1 previous credits: " + user.getCredits());
        int newCredit = user.getCredits() + 5;
        user.setCredits(newCredit);

        log.info("Service1 new credits: " + user.getCredits());
    }
...

@Service("service2")
@Transactional
public class Service2 {

    private UserService userService;

    private static Logger log = Logger.getLogger(Service2.class);

    public void changeCredits() {

         User user = userService.getUserById(1);

        log.info("Service2 previous credits: " + user.getCredits());
        int newCredit = user.getCredits() + 5;
        user.setCredits(newCredit);

        log.info("Service2 new credits: " + user.getCredits());
    }

After running this code, I can see that when both transactions are executed at same time, both transactions are getting the same value from the database. So, they are ignoring the modification of the other one. I suppose this is an error in the transaction isolation:

INFO  27 Feb 2014 14:14:22 - Service1 previous credits: 0
INFO  27 Feb 2014 14:14:22 - Service2 previous credits: 0
INFO  27 Feb 2014 14:14:22 - Service1 new credits: 5
INFO  27 Feb 2014 14:14:22 - Service2 new credits: 5

Here the credits value should be 10.

After this, as both processes are not being executed at same time, the calculation starts being normal:

Feb 27, 2014 2:14:23 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8080"]
Feb 27, 2014 2:14:23 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["ajp-bio-8009"]
Feb 27, 2014 2:14:23 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 10698 ms
INFO  27 Feb 2014 14:14:37 - Service2 previous credits: 5
INFO  27 Feb 2014 14:14:37 - Service2 new credits: 10
INFO  27 Feb 2014 14:14:38 - Service1 previous credits: 10
INFO  27 Feb 2014 14:14:38 - Service1 new credits: 15

I'm working with MYSQL and these are my tasks and transactions configurations:

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="100"/>
<task:scheduler id="myScheduler" pool-size="100"/>

<tx:annotation-driven transaction-manager="transactionManager"/>

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="myDataSource" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
            <prop key="hibernate.show_sql">false</prop>
        </props>
    </property>
    <property name="packagesToScan" value="test.domain" />
</bean>

For some reason my default isolation level is null. I got it from this:

log.info("isolation: " +TransactionSynchronizationManager.getCurrentTransactionIsolationLevel()); 

So, I tried to change the isolation level for both services:

@Service("service1")
@Transactional(isolation=Isolation.REPEATABLE_READ)
public class Service1 {

@Service("service2")
@Transactional(isolation=Isolation.REPEATABLE_READ)
public class Service2 {

The result is still wrong.

Do you have any idea about why this is happening?

Thanks in advance.

1 Answers1

1

In this case @Transaction use the default isolation this depends on the database you are using , try to use isolation property and Isolation.REPEATABLE_READ to obtain the desire behavior.

Enum(Isolation) Enumeration that represents transaction isolation levels for use with the Transactional annotation, corresponding to the TransactionDefinition interface.

http://docs.spring.io/spring/docs/3.2.x/javadoc-api/org/springframework/transaction/annotation/Isolation.html#DEFAULT

A dirty read occurs when a transaction is allowed to read data from a row that has been modified by another running transaction and not yet committed.

Koitoer
  • 18,778
  • 7
  • 63
  • 86
  • Hi Koitoer, Thanks for your answer! I've been trying to change the isolation level for my transactions. However, the result is exactly the same. For doing this, I added the "isolation attribute" to the Transactional annotation in both services: @Transactional(isolation=Isolation.REPEATABLE_READ) Any other idea? Thanks again. – Gonzalo Ron Feb 09 '14 at 04:32
  • I was thining on it yesterday at night, and comes to my mind use lock in the object such as PessimisticLock, but not sure how you are retrieving the objects (I mean the find method in your DAO) could you please paste that code, or even if you are using entitymanger, try to use find with Lock options. – Koitoer Feb 11 '14 at 17:35
  • @GonzaloRon It is strange, I am getting the same issue, I've set all to SERIALIZABLE and is using Postgres with spring jdbc – deFreitas Feb 19 '17 at 12:52