简体   繁体   中英

Unexpected Cache by Hibernate + Spring

I got some weird cache when I am using Spring + Hibernate.

Line A inserts a new row of data into database with year 2012.
Line B get it from DB, finding year is 2012.
Line C updates year to 1970.
Line D finds year is still 2012, I don't understand why would this happen? But if I comment out line B, line D gets 1970, is seems like some kind of cache. Or If in findLock(), I use openSession() instead of getCurrentSession(), line D also gets 1970. Can abybody explain this behavior?

Test Drive

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext-Test.xml")
@TransactionConfiguration(defaultRollback = true,transactionManager="transactionManager")
@Transactional
public class OperationLockDAOTest {

    @Autowired
    private OperationLockDAO operationLockDAO;
    @Autowired
    private APIOperationDAO apiOperationDAO;

    private APIOperation operation;

    @Before
    public void setup() {
        operation = apiOperationDAO.findOperationById(Constants.OP_CREATE_SUBSCRIBER);  
    }

    @Test
    public void testAddNewLockAndReleaseLock() throws DBException{
        String userKey = "testUserKey1" + Math.random();

        boolean bGetLock = operationLockDAO.lockOperationByUser(userKey, operation);//line A, insert 2012
        List<OperationLock> locks1 = operationLockDAO.findLock(userKey, operation);//line B
        OperationLock lock1 = locks1.get(0);
        Calendar cb = Calendar.getInstance();
        cb.setTime(lock1.getModifytime());//get 2012


        operationLockDAO.unlockOperationByUser(userKey, operation);//line C, update to 1970

        List<OperationLock> locks2 = operationLockDAO.findLock(userKey, operation);//line D
        OperationLock lock2 = locks2.get(0);

        Calendar cr = Calendar.getInstance();
        cr.setTime(lock2.getModifytime());
        int crYear = cr.get(Calendar.YEAR);// still get 2012

        assertTrue(crYear == Constants.LongBeforeYear);//assert time stamp of lock is set to 1970 after release 
    }
}

OperationLockDAOImpl.java

@Repository("operationLockDAOImpl")
public class OperationLockDAOImpl implements OperationLockDAO{

    protected static final Logger logger = LoggerFactory.getLogger(OperationLockDAOImpl.class);

    /**
     * return true - this operation is not locked by another thread, lock behavior is successful this time
     *        false - this operation is locked by another thread, lock behavior failed this time
     */
    @SuppressWarnings("unchecked")
    @Override
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public boolean lockOperationByUser(String userKey, APIOperation lockoperation) {

        String selecSsql = "select * from operation_lock where userKey=:userKey and lockoperationId=:lockoperationId";
        Session session = this.factory.getCurrentSession();
        Query selectQuery = session.createSQLQuery(selecSsql).addEntity(OperationLock.class);
        selectQuery.setString("userKey", userKey);
        selectQuery.setLong("lockoperationId", lockoperation.getId());
        List<OperationLock> operationLockList = selectQuery.list();

        if(operationLockList == null || operationLockList.size() == 0){//no record for this userKey and operation, insert one anyway
            String insertSql = "insert into operation_lock(`userKey`,`lockoperationId`,`modifytime`) values (:userKey, :lockoperationId, :updateTime)";
            Query insertQuery = session.createSQLQuery(insertSql);
            insertQuery.setString("userKey", userKey);
            insertQuery.setLong("lockoperationId", lockoperation.getId());
            insertQuery.setTimestamp("updateTime", new Date());
            insertQuery.executeUpdate();
            return true;
        } else {
            return false;
        }
    }

    @Override
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void unlockOperationByUser(String userKey, APIOperation lockoperation) {

        Date currentTime = new Date();
        Calendar time = Calendar.getInstance();
        time.setTime(currentTime);
        time.set(Calendar.YEAR, Constants.LongBeforeYear);//it's long before
        String sql = "update operation_lock set modifytime=:updatetime where userKey=:userKey and lockoperationId=:lockoperationId";
        Session session = this.factory.getCurrentSession();
        Query query = session.createSQLQuery(sql);
        query.setTimestamp("updatetime", time.getTime());
        query.setString("userKey", userKey);
        query.setLong("lockoperationId", lockoperation.getId());
        query.executeUpdate();
    }

    @SuppressWarnings("unchecked")
    @Transactional(isolation = Isolation.SERIALIZABLE)
    @Override
    public List<OperationLock> findLock(String userKey, APIOperation lockoperation) {

        String sql = "select * from operation_lock where userKey=:userKey and lockoperationId=:lockoperationId";
//      Session session = this.factory.openSession();
        Session session = this.factory.getCurrentSession();
        Query query = session.createSQLQuery(sql).addEntity(OperationLock.class);
        query.setString("userKey", userKey);
        query.setLong("lockoperationId", lockoperation.getId());

        List<OperationLock> result =  query.list();
//      session.close();
        return result;

    }
}

I would believe the problem is that Hibernate (and JPA) is not really intended as an interface to native SQL, especially SQL update queries. Hibernate is going to maintain a session-level cache. Typically it knows when entities are updated so that entries don't go stale in the session-cache (at least not within the same thread). However, since you are updating the entity using an SQL update query Hibernate has no idea that you are changing the entity that is in its cache and so it cannot invalidate it.

In summary Hibernate caches do not work with native SQL update queries. To get this to work you would have to maintain each operation in its own independent transaction (remove the @Transactional from the test class) but that will eventually lead to performance problems. The preferred approach to entity modification in Hibernate is something like...

Entity foo = session.get(Entity.class,entityId);
foo.x = y;
Entity fooModified = session.get(Entity.class,entityId);
//fooModified.x == y

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM