Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test pessimistic locking with Spring Boot + Spring Data JPA

I want to test whether the FooRepository.lock() works, after someone called the lock(), others calling it should be wait.

The following is my best try, it doesn't work. I believe the reason is both entityManger and fooRepository are participating in the same transaction.

How to call the lock() from another transaction? Or any suggestion for unit-testing the pessimistic lock? Thanks!!

FooRepositoryTest:

package com.example.demo;

import java.util.UUID;

import javax.persistence.LockModeType;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@DataJpaTest
public class FooRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;    

    @Autowired
    private FooRepository fooRepository;

    @Test
    public void lockTest() {
        // given
        Foo foo = new Foo();
        foo.setName("foo-name");

        UUID fooId = fooRepository.save(foo).getFooId();
        entityManager.flush();
        entityManager.clear();

        // when
        Foo savedFoo = fooRepository.findOne(fooId);
        fooRepository.lock(savedFoo);

        // then
        // I want something like this to be lock wait,
        // something to confirm the above fooRepository.lock() work
        entityManager.getEntityManager().lock(savedFoo, LockModeType.PESSIMISTIC_WRITE);
    }

}

class Foo:

package com.example.demo;

import java.util.UUID;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Foo {  
    @Id
    @GeneratedValue
    private UUID fooId;

    private String name;

    public UUID getFooId() {
        return fooId;
    }

    public void setFooId(UUID fooId) {
        this.fooId = fooId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

class FooApplication:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FooApplication {

    public static void main(String[] args) {
        SpringApplication.run(FooApplication.class, args);
    }
}

class FooRepository:

package com.example.demo;

import java.util.UUID;

import org.springframework.data.jpa.repository.JpaRepository;

public interface FooRepository extends JpaRepository<Foo, UUID>, FooRepositoryCustom {  
}

class FooRepositoryCustom:

package com.example.demo;

public interface FooRepositoryCustom {  
    public void lock(Foo foo);  
}

class FooRepositoryImpl:

package com.example.demo;

import javax.persistence.EntityManager;
import javax.persistence.LockModeType;

import org.springframework.beans.factory.annotation.Autowired;

public class FooRepositoryImpl implements FooRepositoryCustom {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void lock(Foo foo) {
        entityManager.lock(foo, LockModeType.PESSIMISTIC_WRITE);
    }   
}
like image 273
ysl Avatar asked Aug 03 '17 07:08

ysl


People also ask

How do you test for pessimistic locking?

Using the "test-oracle", "test-mysql" or "test-postgresql" profile for your integration tests will allow you to run all pessimistic locking handling tests against your production database. 🔔 This format is suitable for the local environment when you do changes to your pessimistic locking handling.

How do you implement optimistic locking in JPA?

In order to use optimistic locking, we need to have an entity including a property with @Version annotation. While using it, each transaction that reads data holds the value of the version property. Before the transaction wants to make an update, it checks the version property again.


2 Answers

You are getting unit testing wrong.

You do not write unit test to exercise functionality implemented by some 3rd party framework. Unit tests are for your units!

In other words: you do not need to verify that locking works as expected. Your unit does:

entityManager.lock(foo, LockModeType.PESSIMISTIC_WRITE);

so the only thing you could consider testing here: making sure that the entity manager lock() method is called with the expected parameters.

Meaning: verify that your code does use the framework as you think it should be used - but don't get into testing other peoples code! You see - what would you do when your unit test shows that the framework is wrong ... you can't change that! (sure, you can write a bug report then)

Disclaimer: there might be special situations where you assume that some 3rd party product has a bug - then it might be very useful to write a unit test to test this assumption. So that you can later run the unit test against a new version of that product to see if the bug is still present.

like image 106
GhostCat Avatar answered Sep 17 '22 08:09

GhostCat


Testing pessimistic locking handling with integration test is important (may I say mandatory?) for two reasons:

  • There is a good chance that the LockTimeout parameter is not correctly configured (by Oracle and PostgreSQL it is by default infinite time) and this could have dramatic impact of the stability/performance of the system in production;
  • The JPA implementation for pessimistic locking by most RDBMS is very limited, because the 6 internal LockTimeout's parameters differ; Testing against in-memory database only could be not enough and would require special considerations.

In the blog post Testing Pessimistic Locking Handling With SpringBoot and JPA you could:

  • Read more about pessimistic locking handling;
  • And you could find github examples for testing against Oracle, PostgreSQL, MySQL and Apache Derby.

🔔 The Spring Framework is used only as an example. You could easly adapt the tests for every other framework.

like image 25
Andrey Stoev Avatar answered Sep 19 '22 08:09

Andrey Stoev