Некоторые запросы не должны попадать в базу данных постоянно. Например, когда вы запрашиваете основные данные (такие как системные настройки, языки, переводы и т. Д.), Вы можете избегать постоянной отправки одного и того же глупого запроса (и результатов) по сети. Например:
|
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 queryassertEquals(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 resultassertEquals(4, cached.fetchCount(LANGUAGE)); |
Вывод
Кэширование сложно. Очень тяжело. Помимо параллелизма, именования вещей и отдельных ошибок, это одна из трех самых сложных проблем в программном обеспечении.
В этой статье не рекомендуется реализовывать кэш на уровне JDBC. Вы можете или не можете принять это решение самостоятельно. Но когда вы это сделаете, вы увидите, как просто реализовать такой кеш с помощью jOOQ.
И самое лучшее, что вам не нужно использовать jOOQ во всех ваших приложениях. Вы можете использовать его только для этого конкретного варианта использования (и для насмешки JDBC ) и продолжать использовать JDBC, MyBatis, Hibernate и т. Д., Пока вы исправляете соединения JDBC другой инфраструктуры с помощью jOOQ MockConnection.
| Ссылка: | Создайте простой кэш ResultSet JDBC ResultSet с помощью MockDataProvider от jOOQ от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ . |