// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License.  You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
= Using Spring Cache With Apache Ignite

== Overview

Ignite is shipped with `SpringCacheManager` - an implementation of http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html[Spring Cache Abstraction, window=_blank].
It provides an annotation-based way to enable caching for Java methods so that the result of a method execution is stored
in an Ignite cache. Later, if the same method is called with the same set of parameter values, the result will be retrieved
from the cache instead of actually executing the method.

== Enabling Ignite for Spring Caching

Only two simple steps are required to plug in an Ignite cache into your Spring-based application:

* Start an Ignite node with proper configuration in embedded mode (i.e., in the same JVM where the application is running). It can already have predefined caches, but it's not required - caches will be created automatically on first access if needed.
* Configure `SpringCacheManager` as the cache manager in the Spring application context.

The embedded node can be started by `SpringCacheManager` itself. In this case you will need to provide a path to either
the Ignite configuration XML file or `IgniteConfiguration` bean via `configurationPath` or `configuration`
properties respectively (see examples below). Note that setting both is illegal and results in `IllegalArgumentException`.

[tabs]
--
tab:configuration path[]
[source,xml]
----
<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">
    <!-- Provide configuration file path. -->
    <bean id="cacheManager" class="org.apache.ignite.cache.spring.SpringCacheManager">
        <property name="configurationPath" value="examples/config/spring-cache.xml"/>
    </bean>

    <!-- Enable annotation-driven caching. -->
    <cache:annotation-driven/>
</beans>
----
tab:configuration bean[]
[source,xml]
----
<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">
    <-- Provide configuration bean. -->
    <bean id="cacheManager" class="org.apache.ignite.cache.spring.SpringCacheManager">
        <property name="configuration">
            <bean class="org.apache.ignite.configuration.IgniteConfiguration">
                 ...
            </bean>
        </property>
    </bean>

    <-- Enable annotation-driven caching. -->
    <cache:annotation-driven/>
</beans>
----

--

It's possible that you already have an Ignite node running when the cache manager is initialized (e.g., it was started using
`ServletContextListenerStartup`). In this case you should simply provide the grid name via `gridName` property.
Note that if you don't set the grid name as well, the cache manager will try to use the default Ignite instance
(the one with the `null` name). Here is an example:

[tabs]
--
tab:Using an already started Ignite instance[]
[source,xml]
----
<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">
    <!-- Provide grid name. -->
    <bean id="cacheManager" class="org.apache.ignite.cache.spring.SpringCacheManager">
        <property name="gridName" value="myGrid"/>
    </bean>

    <!-- Enable annotation-driven caching. -->
    <cache:annotation-driven/>
</beans>
----
--

[NOTE]
====
[discrete]
Keep in mind that the node started inside your application is an entry point to the whole topology it connects to.
You can start as many remote standalone nodes as you need and all these nodes will participate in caching the data.
====

== Dynamic Caches

While you can have all required caches predefined in Ignite configuration, it's not required. If Spring wants to use a
cache that doesn't exist, the `SpringCacheManager` will automatically create it.

If otherwise not specified, a new cache will be created will all defaults. To customize it, you can provide a configuration
template via `dynamicCacheConfiguration` property. For example, if you want to use `REPLICATED` caches instead of
`PARTITIONED`, you should configure `SpringCacheManager` like this:

[tabs]
--
tab:Dynamic cache configuration[]
[source,xml]
----
<bean id="cacheManager" class="org.apache.ignite.cache.spring.SpringCacheManager">
    ...

    <property name="dynamicCacheConfiguration">
        <bean class="org.apache.ignite.configuration.CacheConfiguration">
            <property name="cacheMode" value="REPLICATED"/>
        </bean>
    </property>
</bean>
----
--

You can also utilize near caches on client side. To achieve this, simply provide near cache configuration via the
`dynamicNearCacheConfiguration` property. By default, near cache is not created. Here is an example:

[tabs]
--
tab:Dynamic near cache configuration[]
[source,xml]
----
<bean id="cacheManager" class="org.apache.ignite.cache.spring.SpringCacheManager">
    ...

    <property name="dynamicNearCacheConfiguration">
        <bean class="org.apache.ignite.configuration.NearCacheConfiguration">
            <property name="nearStartSize" value="1000"/>
        </bean>
    </property>
</bean>
----
--

== Example

Once you have added `SpringCacheManager` to your Spring application context, you can enable caching for any Java method by simply attaching an annotation to it.

Usually, you would use caching for heavy operations, like database access. For example, let's assume you have a DAO class with
`averageSalary(...)` method that calculates the average salary of all employees in an organization. You can use `@Cacheable`
annotation to enable caching for this method:

[tabs]
--
tab:Java[]
[source,java]
----
private JdbcTemplate jdbc;

@Cacheable("averageSalary")
public long averageSalary(int organizationId) {
    String sql =
        "SELECT AVG(e.salary) " +
        "FROM Employee e " +
        "WHERE e.organizationId = ?";

    return jdbc.queryForObject(sql, Long.class, organizationId);
}
----
--

When this method is called for the first time, `SpringCacheManager` will automatically create a `averageSalary` cache.
It will also lookup the pre-calculated average value in this cache and return it right away if it's there. If the average
for this organization is not calculated yet, the method will be called and the result will be stored in cache. So next
time you request the average salary for this organization, you will not need to query the database.

If the salary of one of the employees is changed, you may want to remove the average value for the organization this
employee belongs to, because otherwise the `averageSalary(...)` method will return obsolete cached result. This can be
achieved by attaching `@CacheEvict` annotation to a method that updates employee's salary:

[tabs]
--
tab:Java[]
[source,java]
----
private JdbcTemplate jdbc;

@CacheEvict(value = "averageSalary", key = "#e.organizationId")
public void updateSalary(Employee e) {
    String sql =
        "UPDATE Employee " +
        "SET salary = ? " +
        "WHERE id = ?";

    jdbc.update(sql, e.getSalary(), e.getId());
}
----
--

After this method is called, average value for the provided employee's organization will be evicted from the `averageSalary` cache.
This will force `averageSalary(...)` to recalculate the value next time it's called.

[NOTE]
====
[discrete]
Note that this method receives employee as a parameter, while average values are saved in cache by `organizationID`.
To explicitly specify what is used as a cache key, we used key parameter of the annotation and Spring Expression Language.

The `#e.organizationId` expression means that we need to extract the value of `organizationId` property from `e` variable.
Essentially, `getOrganizationId()` method will be called on provided employee object and the returned value will be used as the cache key.
====
