0
votes

I am working on a school project to demonstrate unrepeatable read error with read_committed isolation level. I am using MySql, and spring with hibernate. Somehow I cannot produce the unrepeatable issue.

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-hibernate.xml");

        WarehouseService service = ctx.getBean("warehouseServiceImpl", WarehouseService.class);
        int prodId = 3;             
        IncreaseInventoryThread thrdIncInvent = new IncreaseInventoryThread(service, prodId, 10);
        thrdIncInvent.start();  

        for(int i=0; i<30; i++){

            service.checkProduct(prodId);
        }
    }

My thread simply keeps increasing product inventory

public class IncreaseInventoryThread extends Thread {

    WarehouseService service;
    int prodId;
    int iteration;


    public IncreaseInventoryThread(WarehouseService service, int prodId, int iteration) {
        this.service = service;
        this.prodId = prodId;
        this.iteration = iteration;
    }


    @Override
    public void run() {
        for(int i=0; i<iteration; i++){
            service.increaseInventory(prodId);

        }
    }
}

And here is my Service Class

@Service
@Transactional(propagation=Propagation.SUPPORTS, readOnly=false)
public class WarehouseServiceImpl implements WarehouseService {


    static final int INITIAL_TOTAL_ORDER = 40;
    static final int DEFAULT_MAX_INVENTORY = 100;
    static final int DEFAULT_INC_INVENTORY = 5;


    @Autowired
    private ProductDao productDao;

    @Override
    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
    public void checkProduct(int prodId){
        Product product = productDao.getProductById(prodId);
            System.out.println("current product "+product.getName() +
                        " is "+ product.getStock());
            if(stock != productDao.getProductById(prodId).getStock()){
                System.out.println("Error: Unrepeatable read");
            }else{
                System.out.println("Read value consistent");
            }
    }
    @Override
    @Transactional(propagation=Propagation.REQUIRED, readOnly=false)
    public void increaseInventory(int prodId) {
        productDao.addInvetory(prodId, DEFAULT_INC_INVENTORY);
        Product product = productDao.getProductById(prodId);
        System.out.println(product.getName()+" now has " + product.getStock());     

    }

My Dao Class has no transaction annotation

@Repository
public class HibernateProductDao implements ProductDao {

    @Autowired 
    private SessionFactory sessionFactory;

    @Override
    public Session currentSession() {
        return this.sessionFactory.getCurrentSession();
    }

    @Override
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Override
    public void addProduct(Product product) {
        currentSession().saveOrUpdate(product);
    }

    @Override
    public Product getProductById(int id) {
        Product product = (Product) currentSession().get(Product.class, id);
        return product;
    }


    @Override
    public void addInvetory(int id, int quantity) {

        Product product = getProductById(id);
        int newStock = product.getStock()+quantity;
        product.setStock(newStock);
        addProduct(product);

    }

}

The idea is to use two threads. One to keep incrementing product inventory, the other thread read product inventory twice within one transaction. Since the read transaction (service.checkProduct(prodId)) is read committed, I should see a unrepeatable read at some point. But for some reason, I cannot produce it. I even add delay between two reads within transaction to no avail. Any suggestion is appreciated. Following the the debug print:

current product TV Set is 100
TV Set now has 105
Read value consistent
current product TV Set is 100
Read value consistent
current product TV Set is 105
Read value consistent
TV Set now has 110
current product TV Set is 110
Read value consistent
TV Set now has 115
current product TV Set is 115
Read value consistent
TV Set now has 120
current product TV Set is 120
Read value consistent
TV Set now has 125
current product TV Set is 125
Read value consistent
TV Set now has 130
current product TV Set is 130
Read value consistent
current product TV Set is 130
Read value consistent
current product TV Set is 130
Read value consistent
TV Set now has 135
current product TV Set is 135
TV Set now has 140
Read value consistent
TV Set now has 145
1

1 Answers

1
votes

Hibernate implements repeatable read isolation implicitly in this case. Even though your transaction has read-committed isolation, once you load an entity into a session, it gets cached there. Any other get operations for that entity return the copy cached in the session, and don't issue another query to the database.

So this

 @Override
    public Product getProductById(int id) {
        Product product = (Product) currentSession().get(Product.class, id);
        return product;
    }

is hitting the database the first time, and returning the entity from the cache the second time. The cached copy won't contain the change from the other thread.

To get the behaviour you want, you need to clear the session. Call currentSession().clear() between the first and second reads.