Некоторые запросы не должны попадать в базу данных постоянно. Например, когда вы запрашиваете основные данные (такие как системные настройки, языки, переводы и т. Д.), Вы можете избегать постоянной отправки одного и того же глупого запроса (и результатов) по сети. Например:
1
|
SELECT * FROM languages |
Большинство баз данных поддерживают буферные кеши для ускорения этих запросов, поэтому вы не всегда обращаетесь к диску. Некоторые базы данных поддерживают кэши набора результатов на курсор, или их драйверы JDBC могут даже реализовывать кэши набора результатов непосредственно в драйвере — например, малоизвестная функция в Oracle :
1
|
SELECT /*+ RESULT_CACHE */ * FROM languages |
Но вы, возможно, не используете Oracle, и поскольку исправление JDBC является проблемой , вы могли прибегнуть к реализации кэша на один или два уровня на уровне доступа к данным или на уровне обслуживания:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
class LanguageService { private Cache cache; List<Language> getLanguages() { List<Language> result = cache.get(); if (result == null ) { result = doGetLanguages(); cache.put(result); } return result; } } |
Делая это в слое JDBC, вместо
Хотя это может хорошо работать на уровне отдельных служб и методов, оно может быстро стать утомительным, когда вы запрашиваете только часть этих результатов. Например, что происходит, когда вы добавляете дополнительный фильтр? Вы также должны кешировать этот запрос? Следует ли выполнять фильтрацию в кеше или использовать базу данных хотя бы один раз для каждого фильтра?
01
02
03
04
05
06
07
08
09
10
11
|
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? } } |
было бы неплохо, если бы у нас был кеш вида:
1
|
Map<String, ResultSet> cache; |
… Который кэширует повторно используемые JDBC ResultSets
(или лучше: jOOQ Results
) и возвращает одинаковые результаты каждый раз, когда встречается идентичная строка запроса.
Для этого используйте MockDataProvider от jOOQ
jOOQ поставляется с MockConnection
, который реализует API-интерфейс JDBC Connection
для вас, насмехаясь над всеми другими объектами, такими как PreparedStatement
, ResultSet
и т. д. Мы уже представили этот полезный инструмент для модульного тестирования в предыдущем сообщении в блоге .
Но вы также можете «смоделировать» ваше соединение, чтобы реализовать кеш! Рассмотрим следующий очень простой MockDataProvider
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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 и предотвратить ResultCache
попадание в базу данных для всех запросов, которые запрашивают из языковой таблицы! Пример использования jOOQ API:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
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, вы столкнетесь с еще одним отсутствием кэша и запрос вернется к базе данных:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
// 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 и т. Д., Пока вы исправляете соединения JDBC другой инфраструктуры с помощью jOOQ MockConnection.
Ссылка: | Создайте простой кэш ResultSet JDBC ResultSet с помощью MockDataProvider от jOOQ от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ . |