使用jOOQ的MockDataProvider破解简单的JDBC ResultSet缓存

某些查询不应该一直访问数据库。 例如,当您查询主数据 (例如系统设置,语言,翻译等)时,您可能希望避免一直通过网络发送相同的愚蠢查询(和结果)。 例如:

SELECT * FROM languages

大多数数据库都维护缓冲区高速缓存以加快这些查询的速度,因此您不必总是打磁盘。 一些数据库为每个游标维护结果集缓存,或者它们的JDBC驱动程序甚至可能直接在驱动程序中实现结果集缓存-例如,Oracle中的一项鲜为人知的功能

SELECT /*+ RESULT_CACHE */ * FROM languages

但是您可能没有使用Oracle, 并且由于修补JDBC很麻烦 ,因此您可能不得不在数据访问或服务层上一层或两层实施高速缓存:

class LanguageService {
    private Cache cache;

    List<Language> getLanguages() {
        List<Language> result = cache.get();

        if (result == null) {
            result = doGetLanguages();
            cache.put(result);
        }

        return result;
    }
}

而是在JDBC层中进行

尽管这在每个服务和方法级别上都可以正常工作,但是当您仅查询那些结果的一部分时,它可能很快就会变得乏味。 例如,当您添加其他过滤器时会怎样? 您是否也应该缓存该查询? 您应该在缓存上执行过滤器,还是每个过滤器至少访问数据库一次?

class LanguageService {
    private Cache cache;

    List<Language> getLanguages() { ... }
    List<Language> getLanguages(Country country) {
        // Another cache?
        // Query the cache only and delegate to
        //     getLanguages()?
        // Or don't cache this at all?
    }
}

如果我们有以下形式的缓存,那会不会很好:

Map<String, ResultSet> cache;

…缓存可重复使用的JDBC ResultSets (或更优:jOOQ Results ),并在每次遇到相同的查询字符串时返回相同的结果。

为此使用jOOQ的MockDataProvider

jOOQ附带了一个MockConnection ,它为您实现了JDBC Connection API, MockConnection了所有其他对象,例如PreparedStatementResultSet等。我们已经在上一篇博客文章中介绍了此有用的工具,用于单元测试

但是您也可以“模拟”您的连接以实现缓存! 考虑以下非常简单的MockDataProvider

class ResultCache implements MockDataProvider {
    final Map<String, Result<?>> cache = 
        new ConcurrentHashMap<>();
    final Connection connection;

    ResultCache(Connection connection) {
        this.connection = connection;
    }

    @Override
    public MockResult[] execute(MockExecuteContext ctx)
    throws SQLException {
        Result<?> result;

        // Add more sophisticated caching criteria
        if (ctx.sql().contains("from language")) {

            // We're using this very useful new Java 8
            // API for atomic cache value calculation
            result = cache.computeIfAbsent(
                ctx.sql(),
                sql -> DSL.using(connection).fetch(
                    ctx.sql(),
                    ctx.bindings()
                )
            );
        }

        // All other queries go to the database
        else {
            result = DSL.using(connection).fetch(
                ctx.sql(), 
                ctx.bindings()
            );
        }

        return new MockResult[] { 
            new MockResult(result.size(), result)
        };
    }
}

显然,这是一个非常简单的示例。 真正的缓存将涉及无效性(基于时间,基于更新等)以及更多的选择性缓存条件,而不仅仅是from language匹配。

但是事实是,使用上述ResultCache ,我们现在可以包装所有JDBC连接,并防止对从语言表中查询的所有查询多次访问数据库! 使用jOOQ API的示例:

DSLContext normal = DSL.using(connection);
DSLContext cached = DSL.using(
    new MockConnection(new ResultCache(connection))
);

// This executs a select count(*) from language query
assertEquals(4, cached.fetchCount(LANGUAGE));
assertEquals(4, normal.fetchCount(LANGUAGE));

// Let's add another language (using normal config):
LanguageRecord lang = normal.newRecord(LANGUAGE);
lang.setName("German");
lang.store();

// Checking again on the language table:
assertEquals(4, cached.fetchCount(LANGUAGE));
assertEquals(5, normal.fetchCount(LANGUAGE));

缓存就像一个魅力! 请注意,当前的缓存实现仅基于SQL字符串(应该如此)。 如果仅对SQL字符串进行少量修改,就会遇到另一个高速缓存未命中的情况,查询将返回数据库:

// This query is not the same as the cached one, it
// fetches two count(*) expressions. Thus we go back
// to the database and get the latest result.
assertEquals(5, (int) cached
    .select(
        count(),
        count())
    .from(LANGUAGE)
    .fetchOne()
    .value1());

// This still has the "stale" previous result
assertEquals(4, cached.fetchCount(LANGUAGE));

结论

缓存很难。 很难。 除了并发,命名和一处错误外,它还是软件中最困难的三个问题之一。

本文不建议在JDBC级别实现缓存。 您可能会自己决定,也可能不会。 但是,当您这样做时,就可以看到使用jOOQ实现这种缓存有多么容易。

最好的是,您不必在所有应用程序中都使用jOOQ。 您可以将其仅用于此特定用例(以及用于模拟JDBC ),并继续使用JDBC,MyBatis,Hibernate等,只要使用jOOQ MockConnection修补其他框架的JDBC连接即可。

翻译自: https://www.javacodegeeks.com/2015/03/hack-up-a-simple-jdbc-resultset-cache-using-jooqs-mockdataprovider.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值