3

The following code works, however if I remove the flush() and clear() calls, then the second call to showTable() does not show the updated name "joan", but "john".

What is the proper way to achieve this, without calling flush() and clear()?

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.example.Customer;

@ContextConfiguration(locations = {"classpath:spring/test/spring-test.xml"})
@TransactionConfiguration(transactionManager = "txManager")
public class Test extends AbstractTransactionalJUnit4SpringContextTests {
    private final Logger log = LoggerFactory.getLogger(getClass());

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void test() throws Exception {
        Customer customer = new Customer();
        customer.setName("john");

        entityManager.persist(customer);
        entityManager.flush();
        entityManager.clear();

        showTable();

        final Query query = entityManager.createQuery("update Customer c set c.name='joan'");
        int updateCount = query.executeUpdate();
        log.debug("Update count: " + updateCount);
        entityManager.flush();
        entityManager.clear();

        showTable();
    }

    public void showTable() {
        final Query query = entityManager.createQuery("select c FROM Customer c");
        List<Customer> list = query.getResultList();
        for (Customer customer: list) {
            log.info("customer name: " + customer.getName());
        }
    }
}
David Portabella
  • 12,390
  • 27
  • 101
  • 182

1 Answers1

5

The first test shouldn't need flush or clear, because the entity manager must detect that the query results could be affected by the pending changes not already saved. So you shouldn't have to flush(), and even less to clear(), before executing the query.

The second test, however, is different. You're executing an update query, and thoses queries completely bypass the first level cache. They make changes in the database behind its back. This is the reason why you need to clear: if you don't, the select query will find the customer that is already in the cache, and the cached (but obsolete) entity will be returned.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • 1
    correct, the first flush/clear is not needed. however, the question is about what is the proper way to make an update query without needing to flush/clear, which is very error prone. maybe it is possible to disable the "first level cache" somehow? or rewrite the update query in some other way (iterating over object and update/merging)? – David Portabella May 30 '12 at 07:55
  • 1
    Such DML update queries should be used very rarely. You typically update entities by finding them (using a `em.find()` or a select query), and modifying their state. And JPA automatically flushed the changes to the database. Your query sets the name of all the customers in the database to the same value, which is very far from a typical use-case. If a real use-case needs such a query, then you'll do what you did in your question: clear the cache after the massive update. – JB Nizet May 30 '12 at 08:23