Главная » Хабрахабр » Кеширование данных — Java Spring

Кеширование данных — Java Spring

Многократно вычитывая одни и те же данные, встает вопрос оптимизации, данные не меняются или редко меняются, это различные справочники и др. информация, т.е. функция получения данных по ключу — детерминирована. Тут наверно все понимают — нужен Кеш! Зачем всякий раз повторно выполнять поиск данных или вычисление?

Так вот здесь я покажу как делать кеш в Java Spring и поскольку это тесно связанно скорее всего с Базой данных, то и как сделать это в СУБД на примере одной конкретной.

Содержание

  • Кеш в Spring
  • Кеш в Oracle PL-SQL функции

Кеш в Spring

Далее все поступают примерно одинаково, в Java используют различные HasMap, ConcurrentMap и др. В Spring тоже для это есть решение, простое, удобное, эффективное. Я думаю что в большинстве случаев это поможет в решении задачи. И так, все что нужно, это включить кеш и аннотировать функцию.

Делаем кеш доступным

@SpringBootApplication
@EnableCaching
public class DemoCacheAbleApplication
}

Кешируем данные поиска функции

@Cacheable(cacheNames="person") public Person findCacheByName(String name) { //...
}

В аннотации указывается название кеша и есть еще другие параметры. Работает как и ожидается так, первый раз код выполняется, результат поиска помещается в кеш по ключу (в данном случае name) и последующие вызовы код уже не выполняется, а данные извлекаются из кеша.

Пример реализации репозитория «Person» с использованием кеша

@Component
public class PersonRepository { private static final Logger logger = LoggerFactory.getLogger(PersonRepository.class); private List<Person> persons = new ArrayList<>(); public void initPersons(List<Person> persons) { this.persons.addAll(persons); } private Person findByName(String name) { Person person = persons.stream() .filter(p -> p.getName().equals(name)) .findFirst() .orElse(null); return person; } @Cacheable(cacheNames="person") public Person findCacheByName(String name) { logger.info("find person ... " + name); final Person person = findByName(name); return person; }
}

Проверяю что получилось

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoCacheAbleApplicationTests { private static final Logger logger = LoggerFactory.getLogger(DemoCacheAbleApplicationTests.class); @Autowired private PersonRepository personRepository; @Before public void before() { personRepository.initPersons(Arrays.asList(new Person("Иван", 22), new Person("Сергей", 34), new Person("Игорь", 41))); } private Person findCacheByName(String name) { logger.info("begin find " + name); final Person person = personRepository.findCacheByName(name); logger.info("find result = " + person.toString()); return person; } @Test public void findByName() { findCacheByName("Иван"); findCacheByName("Иван"); }
}

В тесте вызываю два раза

@Test
public void findByName() { findCacheByName("Иван"); findCacheByName("Иван");
}

, первый раз происходит вызов, поиск, в второй раз результат берется уже из кеша. Это видно в консоли

image

Если в функции более одного аргумента, то можно указать имя параметра, какой использовать в качестве ключа. Удобно, можно точечно оптимизировать существующий функционал.

@Cacheable(cacheNames="person", key="#name") public Person findByKeyField(String name, Integer age) {

Есть и более сложные схемы получения ключа, это в документации.

Для этой цели есть две аннотации. Но конечно встанет вопрос, как обновить данные в кеше?

Первая это @CachePut

Функция с этой аннотацией будет всегда вызывать код, а результат помещать в кеш, таким образом она сможет обновить кеш.

Добавлю в репозиторий два метода: удаления и добавления Person

public boolean delete(String name) { final Person person = findByName(name); return persons.remove(person); } public boolean add(Person person) { return persons.add(person); }

Выполню поиск Person, удалю, добавлю, опять поиск, но по прежнему буду получать одно и тоже лицо из кеша, пока не вызову «findByNameAndPut»

@CachePut(cacheNames="person") public Person findByNameAndPut(String name) { logger.info("findByName and put person ... " + name); final Person person = findByName(name); logger.info("put in cache person " + person); return person; }

Тест

@Test public void findCacheByNameAndPut() { Person person = findCacheByName("Иван"); logger.info("delete " + person); personRepository.delete("Иван"); findCacheByName("Иван"); logger.info("add new person"); person = new Person("Иван", 35); personRepository.add(person); findCacheByName("Иван"); logger.info("put new"); personRepository.findByNameAndPut("Иван"); findCacheByName("Иван"); }

image

Другая аннотация это @CacheEvict

Этот процесс полезен для удаления устаревших или неиспользуемых данных из кеша. Позволяет не просто посещать хранилище кеша, но и выселять.

По умолчанию Spring для кеша использует — ConcurrentMapCache, если есть свой отличный класс для организации кеша, то это возможно указать в CacheManager

@SpringBootApplication
@EnableCaching
public class DemoCacheAbleApplication { public static void main(String[] args) { SpringApplication.run(DemoCacheAbleApplication.class, args); } @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(Arrays.asList( new ConcurrentMapCache("person"), new ConcurrentMapCache("addresses"))); return cacheManager; }
}

Там же указываются имена кешей, их может быть несколько. В xml конфигурации это указывается так:

Spring configuration.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven/> <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="person"/> <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="addresses"/> </set> </property> </bean> </beans>

Person класс

public class Person { private String name; private Integer age; public Person(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public Integer getAge() { return age; } @Override public String toString() { return name + ":" + age; }

Структура проекта

image

Здесь pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demoCacheAble</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>DemoCacheAble</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>

Кеш в Oracle PL-SQL функции

Ну и в конце, тем кто не пренебрегает мощностью СУБД, а использует ее, могут использовать кеширование на уровне БД, в дополнение или как альтернативу. Так например в Oracle не менее элегантно можно превратить обычную функцию, в функцию с кешированием результата, добавив к ней

RESULT_CACHE

Пример:

CREATE OR REPLACE FUNCTION GET_COUNTRY_NAME(P_CODE IN VARCHAR2) RETURN VARCHAR2 RESULT_CACHE IS CODE_RESULT VARCHAR2(50);
BEGIN SELECT COUNTRY_NAME INTO CODE_RESULT FROM COUNTRIES WHERE COUNTRY_ID = P_CODE; -- имитация долгой работы dbms_lock.sleep (1); RETURN(CODE_RESULT);
END;

После изменения данных в таблице, кеш будет перестроен, можно тонко настроить правило кеша с помощью

RELIES_ON(...)

Материалы
Cache Abstraction


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] Введение в ptrace или инъекция кода в sshd ради веселья

Конечно, это несколько искусственная задача, так как есть множество других, более эффективных, способов достичь желаемого (и с гораздо меньшей вероятностью получить SEGV), однако, мне показалось клёвым сделать именно так. Цель, которой я задался, была весьма проста: узнать введённый в sshd ...

Дайджест свежих материалов из мира фронтенда за последнюю неделю №339 (12 — 18 ноября 2018)

Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.     Медиа    |    Веб-разработка    |    CSS    |    Javascript    |    Браузеры    |    Занимательное Медиа • Подкаст «Frontend Weekend» #79 – Олег Поляков об основании CodeDojo и о том, как это стало основным местом работы• Подкаст «Пятиминутка React» ...