java MyBatis:如何绕过本地缓存并在特定选择上直接命中数据库

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26440362/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 09:55:47  来源:igfitidea点击:

MyBatis: how to bypass a local cache and directly hit the DB on specific select

javaspringmybatis

提问by Alexander

I use MyBatis 3.1.
I have two use cases when I need to bypass MyBatis local cache and directly hit the DB.
Since MyBatis configuration file only have global settings, it is not applicable to my case, because I need it as an exception, not as a default. Attributes of MyBatis <select> XML statement do not seem to include this option.

我使用 MyBatis 3.1。
当我需要绕过 MyBatis 本地缓存并直接命中 DB 时,我有两个用例。
由于 MyBatis 配置文件只有全局设置,所以不适用于我的情况,因为我需要它作为例外,而不是作为默认值。MyBatis <select> XML 语句的属性似乎没有包含这个选项。

Use case 1: 'select sysdate from dual'.
MyBatis caching causes this one to always return the same value within a MyBatis session. This causes an issue in my integration test, when I try to replicate a situation of an outdated entry.
My workaround was just to use a plain JDBC call.

用例 1:'select sysdate from dual'。
MyBatis 缓存导致这个在 MyBatis 会话中总是返回相同的值。当我尝试复制过时条目的情况时,这会导致我的集成测试出现问题。
我的解决方法只是使用普通的 JDBC 调用。

Use case 2: 'select' from one thread does not always see the value written by another thread.
Thread 1:

用例 2:从一个线程“选择”并不总是看到另一个线程写入的值。
主题 1:

SomeObject stored = dao.insertSomeObject(obj);
runInAnotherThread(stored.getId());
//complete and commit

Thread 2:

主题 2:

//'id' received as an argument provided to 'runInAnotherThread(...)'
SomeObject stored = dao.findById(id);
int count = 0;
while(stored == null && count < 300) {
    ++count;
    Thread.sleep(1000);
    stored = dao.findById(id);
}
if (stored == null) {
    throw new MyException("There is no SomeObject with id="+id);
}

I occasionally receive MyException errors on a server, but can't reproduce on my local machine. In all cases the object is always in the DB. So I guess the error depends on whether the stored object was in MyBatis local cache at the first time, and waiting for 5 minutes does not help, since it never checks the actual DB.

我偶尔会在服务器上收到 MyException 错误,但无法在我的本地机器上重现。在所有情况下,对象始终在 DB 中。所以我猜错误取决于第一次存储的对象是否在MyBatis本地缓存中,等待5分钟无济于事,因为它从不检查实际的DB。

So my question is how to solve the above use cases within MyBatis without falling back to the plain JDBC?
Being able just to somehow signal MyBatis not to use a cached value in a specific call (the best) or in all calls to a specific query would be the preferred option, but I will consider any workaround as well.

所以我的问题是如何在 MyBatis 中解决上述用例而不回退到普通的 JDBC?
能够以某种方式向 MyBatis 发出信号,不要在特定调用(最好)或对特定查询的所有调用中使用缓存值将是首选选项,但我也会考虑任何解决方法。

回答by Roman Konoval

I don't know a way to bypass local cache but there are two options how to achieve what you need.

我不知道绕过本地缓存的方法,但有两种方法可以实现您的需求。

The first option is to set flushCache="true"on select. This will clear the cache after statement execution so next query will hit database.

第一个选项是设置flushCache="true"on select。这将在语句执行后清除缓存,以便下一个查询将命中数据库。

    <select id="getCurrentDate" resultType="date" flushCache="true">
        SELECT SYSDATE FROM DUAL
    </select>  

Another option is to use STATEMENT level local cache. By default local cache is used during SESSION (which is typically translates to transaction). This is specified by localCacheScopeoption and is set per session factory. So this will affect all queries using this mybatis session factory.

另一种选择是使用 STATEMENT 级别的本地缓存。默认情况下,在 SESSION 期间使用本地缓存(通常转换为事务)。这由localCacheScope选项指定并按会话工厂设置。所以这会影响所有使用这个mybatis session factory的查询。

回答by Alexander

Let me summarize.
The solution from the previous answer, 'flushCache="true"' option on the query, works and solves both use cases. It will flush cache after every such 'select', so the next 'select' statement will hit the DB. Although it works afterthe 'select' statement is executed, it's OK since the cache is empty anyway before the first 'select'.

让我总结一下。
上一个答案的解决方案,' flushCache="true"' 查询选项,可以解决这两个用例。它会在每次这样的“选择”之后刷新缓存,因此下一个“选择”语句将命中数据库。尽管它执行 'select' 语句工作,但没关系,因为在第一个 'select' 之前缓存是空的。

Another solution is to start a new session. I use Spring, so it's enough to mark a method with @Transactional(propagation = Propagation.REQUIRES_NEW). Since MyBatis session is tied to Spring transaction, this will cause to create another MyBatis session with fresh cache every time the method is called.

另一种解决方案是开始一个新的会话。我使用 Spring,所以用@Transactional(propagation = Propagation.REQUIRES_NEW)标记一个方法就足够了。由于 MyBatis 会话绑定到 Spring 事务,这将导致每次调用该方法时都会创建另一个带有新缓存的 MyBatis 会话。

By some reason, the MyBatis option 'useCache="false"' in the query does not work.

由于某种原因,查询中的 MyBatis 选项 ' useCache="false"' 不起作用。

回答by Buggy man

The following Optionsannotation can be used:

Options可以使用以下注释:

@Options(useCache=false, flushCache=FlushCachePolicy.TRUE)

回答by SiddP

Apart from answers by Roman and Alexander there is one more solution for this:

除了 Roman 和 Alexander 的回答之外,还有另一种解决方案:

 Configuration configuration = MyBatisUtil.getSqlSessionFactory().getConfiguration(); 
            Collection<Cache> caches = configuration.getCaches(); 

           //If you have multiple caches and want a particular to get deleted.           
          // Cache cache = configuration.getCache("PPL"); // namespace of particular XML
            for (Cache cache : caches) { 
                Lock w = cache.getReadWriteLock().writeLock(); 
                w.lock(); 
                try { 
                    cache.clear(); 
                } finally { 
                    w.unlock(); 
                } 
            }