Статьи

Кэширование поверх MyBatis: широко используемая реализация Ehcache с MyBatis

Эта статья представляет собой первое доказательство концепции из серии, описанной в предыдущей статье 4 Практические подходы для улучшения реализации уровня доступа к данным, и представляет, как внедрить Ehcache поверх MyBatis, как добиться оптимальной конфигурации для него и личные мнения пользователей. Автор о выбранном подходе для уровня доступа к данным.

В ходе моего исследования кеширования через MyBatis я обнаружил, что Ehcache является первым вариантом среди разработчиков, когда им необходимо реализовать механизм кэширования через MyBatis с использованием сторонней библиотеки. Ehcache, вероятно, настолько популярен, потому что он представляет собой кэш на основе Java с открытым исходным кодом, доступный по лицензии Apache 2. Кроме того, он масштабируется от внутрипроцессного с одним или несколькими узлами до смешанной внутрипроцессной / внепроцессной конфигурации с кэшем размером в терабайт. Кроме того, для тех приложений, которым требуется согласованный распределенный кэш, Ehcache использует Terracotta Server Array с открытым исходным кодом. И последнее, но не менее важное, среди его последователей — Фонд Викимедиа, который использует Ehcache для повышения производительности своих вики-проектов.

В рамках этой статьи будут рассмотрены следующие аспекты:

1. Какую выгоду получит приложение от кэширования с использованием Ehcache? Особенности Ehcache будут подробно описаны в этом разделе.

2 . Практическая реализация проекта EhCachePOC — в этом разделе ключевые концепции EhCache будут рассмотрены в практической реализации.

3. Резюме — Как производительность приложения была улучшена после этой реализации?

Код всех проектов, которые будут реализованы, можно найти по адресу  https://github.com/ammbra/CacherPoc   или, если вы заинтересованы только в текущей реализации, вы можете получить к нему доступ здесь:  https://github.com/ammbra. / CacherPoc / дерево / ведущий / EhCachePoc 

Какую выгоду получит приложение от кэширования с использованием Ehcache?

Время, необходимое приложению для обработки запроса, в основном зависит от скорости процессора и основной памяти. Чтобы «ускорить» ваше приложение, вы можете выполнить одно или несколько из следующих действий:

  •  улучшить производительность алгоритма
  •  добиться распараллеливания вычислений на нескольких процессорах или на нескольких компьютерах
  •  обновить скорость процессора

Как объяснялось в предыдущей статье, приложения высокой доступности должны выполнять небольшое количество действий с базой данных. Поскольку время, необходимое для выполнения вычисления, зависит главным образом  от скорости, с которой могут быть получены данные, тогда приложение должно иметь возможность временно сохранять вычисления, которые могут быть снова использованы повторно. Кэширование может уменьшить требуемую рабочую нагрузку, это означает, что механизм кеширования должен быть создан!

Ehcache описывается как:

  • Быстрый и легкий вес, имеющий простой API и требующий только зависимости от SLF4J.
  • Масштабируется до сотен узлов с помощью серверного массива Terracotta, а также потому, что обеспечивает память и дисковое хранилище для масштабируемости в гигабайты
  • Гибкость, потому что поддерживает Object или Serializable кэширование; также предоставляет политики удаления LRU, LFU и FIFO
  • Основанный на стандартах, имеющий полную реализацию JSR107 JCACHE API
  • Application Persistence Provider, поскольку он предлагает постоянное дисковое хранилище, которое хранит данные между перезапусками виртуальных машин.
  • JMX включен
  • Средство распределенного кэширования, поскольку оно предлагает кластерное кэширование через терракоту и реплицированное кэширование через RMI, JGroups или JMS
  • Сервер кэширования (RESTful, Сервер кэширования SOAP)
  • Совместимость с поиском, наличие автономного и распределенного поиска с использованием свободного языка запросов

Практическая реализация проекта EhCachePOC

Реализация EhCachePoc будет выглядеть так, как описано на схеме ниже:

Чтобы протестировать производительность Ehcache через проект POC (подтверждение концепции), выполняется следующая настройка проекта:

1. Создайте новый проект Maven EJB из вашей IDE (этот тип проекта является платформой, предоставляемой NetBeans, но для тех, кто использует eclipse,  вот  полезное руководство). В статье этот проект называется EhCachePOC.

2. Отредактируйте pom проекта, добавив необходимые банки:

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.6</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>     
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>  

3. Добавьте драйвер подключения к базе данных, в этом случае apache derby:

        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derbyclient</artifactId>
            <version>10.11.1.1</version>
        </dependency>

4. Запустите команды mvn clean и mvn install для вашего проекта.

Теперь настройка проекта готова, давайте продолжим реализацию MyBatis:

1. Настройте в папке resources / com / tutorial / ehcachepoc / xml файл Configuration.xml следующим образом :

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.apache.derby.jdbc.ClientDriver"/>
                <property name="url" value="dburl"/>
                <property name="username" value="cruddy"/>
                <property name="password" value="cruddy"/>
            </dataSource>
        </environment>         
    </environments>
    <mappers>               
        <!--<mapper resource="com/tutorial/ehcachepoc/xml/EmployeeMapper.xml" />-->
    </mappers>                   
</configuration>

2. Создайте в Java свою собственную реализацию SQLSessionFactory. Например, создайте что-то похожее на   com.tutorial.ehcachepoc.config. SQLSessionFactory:

public class SQLSessionFactory {
    private static final SqlSessionFactory FACTORY;
 
    static {
        try {
            Reader reader = Resources.getResourceAsReader("com/tutorial/ehcachepoc/xml/Configuration.xml");
            FACTORY = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e){
            throw new RuntimeException("Fatal Error.  Cause: " + e, e);
        }
    }
  public static SqlSessionFactory getSqlSessionFactory() {
        return FACTORY;
    }
}

3. Создайте необходимые классы компонентов, которые будут отображаться в ваших результатах sql, например Employee :

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String firstName;
    private String lastName;
    private String adress;
    private Date hiringDate;
    private String sex;
    private String phone;
    private int positionId;
    private int deptId;

    public Employee() {
    }

    public Employee(Integer id) {
        this.id = id;
    }


    @Override
    public String toString() {
        return "com.tutorial.ehcachepoc.bean.Employee[ id=" + id + " ]";
    }
}

4. Создайте интерфейс IEmployeeDAO, который будет предоставлять реализацию ejb при внедрении:

public interface IEmployeeDAO {
        public List<Employee> getEmployees();   
}

5. Реализуйте вышеуказанный интерфейс и представьте реализацию как EJB без состояния (этот вид EJB сохраняет только свое состояние, но нет необходимости сохранять его связанное состояние клиента):

@Stateless(name = "ehcacheDAO")
@TransactionManagement(TransactionManagementType.CONTAINER)
public class EmployeeDAO implements IEmployeeDAO {

    private static Logger logger = Logger.getLogger(EmployeeDAO.class);
    private SqlSessionFactory sqlSessionFactory;

    @PostConstruct
     public void init() {
        sqlSessionFactory = SQLSessionFactory.getSqlSessionFactory();
    }


    @Override
    public List<Employee> getEmployees() {
        logger.info("Getting employees.....");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<Employee> results = sqlSession.selectList("retrieveEmployees");
        sqlSession.close();
        return results;
    }
}

5. Создайте EmployeeMapper.xml,  который содержит запрос с именем «retrieveEmployees»

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.tutorial.ehcachepoc.mapper.EmployeeMapper" >
    <resultMap id="results" type="com.tutorial.ehcachepoc.bean.Employee" >
        <id column="id" property="id" javaType="integer" jdbcType="BIGINT" />
        <result column="first_name" property="firstName" javaType="string" jdbcType="VARCHAR"/>
        <result column="last_name" property="lastName" javaType="string" jdbcType="VARCHAR"/>
        <result column="hiring_date" property="hiringDate" javaType="date" jdbcType="DATE" />
        <result column="sex" property="sex" javaType="string" jdbcType="VARCHAR" />
        <result column="dept_id" property="deptId" javaType="integer" jdbcType="BIGINT" />
    </resultMap> 
 
    <select id="retrieveEmployees" resultMap="results" >
        select id, first_name, last_name, hiring_date, sex, dept_id
        from employee   
    </select>
</mapper>  													

Если вы помните настройку CacherPOC из предыдущей статьи, то вы можете протестировать свою реализацию, если добавите проект EhCachePOC в качестве зависимости и вставите IEmployeeDAO в EhCacheServlet. Ваш файл CacherPOC pom.xml должен содержать:

       <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>EhCachePoc</artifactId>
            <version>${project.version}</version>
        </dependency>	

и ваш сервлет должен выглядеть так:

@WebServlet("/EhCacheServlet")
public class EhCacheServlet extends HttpServlet {
   private static Logger logger = Logger.getLogger(EhCacheServlet.class);

    @EJB(beanName ="ehcacheDAO")
    IEmployeeDAO employeeDAO;
    private static final String LIST_USER = "/listEmployee.jsp";

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String forward= LIST_USER;
        List<Employee> results = new ArrayList<Employee>();
         for (int i = 0; i < 10; i++) {
            for (Employee emp : employeeDAO.getEmployees()) {
                logger.debug(emp);
                results.add(emp);
            }
            
            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                logger.error(e, e);
            }
        }
        req.setAttribute("employees", results);
        RequestDispatcher view = req.getRequestDispatcher(forward);
        view.forward(req, resp);
    }
    
}			

Запустите реализацию CacherPoc, чтобы проверить, работает ли ваш уровень доступа к данным с MyBatis, или загрузите код, предоставленный по адресу  https://github.com/ammbra/CacherPoc.  

Но если в базе данных хранится большое количество сотрудников или, возможно, получение числа 10xemployeesNo представляет большую нагрузку для базы данных. Также можно заметить, что запрос из EmployeeMapper.xml извлекает данные, которые почти никогда не изменяются (id, first_name, last_name, hiring_date, sex не могут измениться; единственное значение, которое может измениться во времени, это dept_id); поэтому можно использовать механизм кэширования.

Ниже описано, как этого можно добиться с помощью EhCache:

1. Настройте непосредственно в папке ресурсов файл ehcache.xml с помощью:

<?xml version="1.0" encoding="UTF-8"?>
<!--
    caching configuration
-->
<ehcache>   
    <defaultCache eternal="true" maxElementsInMemory="1000" timeToIdleSeconds="3600"
        timeToLiveSeconds="3600" maxEntriesLocalHeap="1000" maxEntriesLocalDisk="10000000"
        memoryStoreEvictionPolicy="LRU" statistics="true" />

</ehcache>

В этом xml объясняется, что хранилище памяти используется для стратегии кэширования LRU (Последнее использованное недавно), задаются ограничения на количество элементов, разрешенных для хранения, их время простоя и время жизни.

Стратегия Memory Store часто выбирается потому, что она быстра и поточно-ориентирована для использования несколькими параллельными потоками при поддержке LinkedHashMap. Также все элементы, участвующие в процессе кэширования, пригодны для размещения в хранилище памяти.

Можно попробовать другой подход: хранение кеша на диске. Это можно сделать, заменив содержимое тега ehcache следующим:

diskStore path="F:\\cache" />
     
    <defaultCache eternal="true" maxElementsInMemory="1000"
                   overflowToDisk="true" diskPersistent="true" timeToIdleSeconds="0"
                   timeToLiveSeconds="0" memoryStoreEvictionPolicy="LRU" statistics="true" />

В отличие от стратегии хранилища памяти, реализация хранилища дисков подходит только для элементов, которые можно сериализовать и помещать в автономную кучу; если обнаружены какие-либо не сериализуемые элементы, они будут удалены и будет выдано сообщение журнала уровня WARNING. Выселение производится с использованием алгоритма LFU и не может быть изменено или изменено. С точки зрения постоянства, этот метод кэширования позволяет  управлять кэшем с помощью постоянной конфигурации диска; если false или пропущено, хранилище диска не будет сохраняться между перезапусками CacheManager.

2. Обновите EmployeeMapper.xml, чтобы использовать ранее реализованную стратегию кэширования:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.tutorial.ehcachepoc.mapper.EmployeeMapper" >
   
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
     
    <resultMap id="results" type="com.tutorial.ehcachepoc.bean.Employee" >
        <id column="id" property="id" javaType="integer" jdbcType="BIGINT" />
        <result column="first_name" property="firstName" javaType="string" jdbcType="VARCHAR"/>
        <result column="last_name" property="lastName" javaType="string" jdbcType="VARCHAR"/>
        <result column="hiring_date" property="hiringDate" javaType="date" jdbcType="DATE" />
        <result column="sex" property="sex" javaType="string" jdbcType="VARCHAR" />
        <result column="dept_id" property="deptId" javaType="integer" jdbcType="BIGINT" />
    </resultMap> 
 
    <select id="retrieveEmployees" resultMap="results"  useCache="true">
        select id, first_name, last_name, hiring_date, sex, dept_id from employee   
    </select>  
</mapper>

Добавив строку  <cache type = «org.mybatis.caches.ehcache.EhcacheCache» />  и указав в запросе useCache = «true», вы связываете конфигурацию ehcache.xml с вашей реализацией DataAccessLayer.

Очистка, сборка и повторное развертывание проектов EhCachePOC и CacherPoc; Теперь дважды найдите ваших сотрудников, чтобы в кеше памяти можно было хранить ваши значения. Когда вы запускаете запрос в первый раз, ваше приложение выполнит запрос к базе данных и получит результаты. Во второй раз, когда вы получите доступ к списку сотрудников, ваше приложение получит доступ к памяти в памяти.

Резюме — Как производительность приложения была улучшена после этой реализации?

Производительность приложения зависит от множества факторов

  • how many times a cached piece of data can and is reduced by the application
  • the proportion of the response time that is alleviated by caching

Amdhal’s law 
 can be used to estimate the system’s speed up :

 where P is proportion speed up and S is speed up.

Let’s take the application from this article as example and calculate the speed up.

When the application ran the query without caching,a JDBC transaction is performed and  in your log will be something similar to :

INFO:   2014-11-27 18:01:30,020 [EmployeeDAO] INFO  com.tutorial.hazelcastpoc.dao.EmployeeDAO:38 - Getting employees.....
INFO:   2014-11-27 18:01:39,148 [JdbcTransaction] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:98 - Setting autocommit to false on JDBC Connection [org.apache.derby.client.net.NetConnection40@1c374fd]
INFO:   2014-11-27 18:01:39,159 [retrieveEmployees] DEBUG com.tutorial.hazelcastpoc.mapper.EmployeeMapper.retrieveEmployees:139 - ==>  Preparing: select id, first_name, last_name, hiring_date, sex, dept_id from employee
INFO:   2014-11-27 18:01:39,220 [retrieveEmployees] DEBUG com.tutorial.hazelcastpoc.mapper.EmployeeMapper.retrieveEmployees:139 - ==> Parameters:
INFO:   2014-11-27 18:01:39,316 [retrieveEmployees] DEBUG com.tutorial.hazelcastpoc.mapper.EmployeeMapper.retrieveEmployees:139 - <==      Total: 13

while running the queries with Ehcache caching the JDBC transaction is performed only once (to initialize the cache) and after that the log will look like :

INFO:   2014-11-28 18:04:50,020 [EmployeeDAO] INFO  com.tutorial.ehcachepoc.dao.EmployeeDAO:38 - Getting employees.....
INFO:   2014-11-28 18:04:50,020 [EhCacheServlet] DEBUG com.tutorial.cacherpoc.EhCacheServlet:41 - com.tutorial.crudwithjsp.model.Employee[ id=1 ]

Let’s look at the time that each of our 10 times requests has scored:

  • the first not cached version of 10 times requests took about  57 seconds and 51 milliseconds,
  • while the cached requests scored a time of 27seconds and 86 miliseconds.

In order to apply Amdhal’s law for the system the following input is needed:

  • Un-cached page time:  60 seconds
  • Database time : 58 seconds
  • Cache retrieval time: 28seconds
  • Proportion: 96.6% (58/60) (P)

The expected system speedup is thus:

 1 / (( 1 – 0.966) + 0.966 / (58/28)) = 1 / (0.034 + 0. 966/2.07) =  2 times system speedup

This result can be improved of course, but the purpose of this article was to prove that caching using Ehcache over MyBatis offers a significant improvement to what used to be available before its implementation.

Learn more from: