Spring Data Redis Cache
Redis Caching
Redis is a datastore, not just a cache mechanism, but like most folks, I am going to use it to cache some data. I have an app that will be pulling data from a REST service frequently, but the service’s latency is causing some response issues with my site, so I am going to build a separate polling service that will pull the data from the REST service every n seconds and then cache it. This will run in the background, like a daemon. Then, I will replace those slow service calls in my user-facing app with the faster cache calls.
I learned how to build @Scheduled tasks yesterday, so today, I’m going to learn the REDIS part.
Getting Started
First thing I did was go to the redis tutorial and run through the examples. It’s a nice, short 5-minute clever interactive tutorial. [think I put enough adjectives on that tutorial?]
Next, install it (or just use a docker image). I run Ubuntu on Windows, so I was able to follow the download instructions. Two caveats: 1) I had to put sudo in front of all the commands in the installation steps, 2) I kept getting a permissions denied error on the wget, so I manually downloaded the tarball and copied it to the /var/lib directory.
Start the Server
/var/lib/redis-4.0.9/src/redis-server
Start the CLI Client
/var/lib/redis-4.0.9/src/redis-cli
Ping the Server
OK, I got some config warnings, but it the ping worked. We’re in business.
Add redis caching to a Spring Boot app
Instead of building a separate hello-world cache example for my toolbox, I am adding the caching to the scheduled task I built yesterday.
Configure the connection and the template
package com.scotthensen.toolbox.scheduledtask.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Bean
JedisConnectionFactory jedisConnectionFactory() {
//For Prod, set up a pool and use the stand-alone configuration
//RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("localhost",6379);
//return new JedisConnectionFactory(config);
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<String, Object> redisStrObjTemplate() {
final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
}
Build a quote class to store in redis
package com.scotthensen.toolbox.scheduledtask.cache;
import java.math.BigDecimal;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
import com.scotthensen.toolbox.scheduledtask.domain.StockQuote;
import lombok.Data;
import lombok.NoArgsConstructor;
@RedisHash("Quote")
@Data
@NoArgsConstructor
public class Quote {
@Id
private String id;
@Indexed
private String symbol;
private String companyName;
private String primaryExchange;
private String sector;
private String calculationPrice;
private BigDecimal open;
private BigDecimal openTime;
private BigDecimal close;
private BigDecimal closeTime;
private BigDecimal high;
private BigDecimal low;
private BigDecimal latestPrice;
private String latestSource;
private String latestTime;
private BigDecimal latestUpdate;
private BigDecimal latestVolume;
private BigDecimal iexRealtimePrice;
private BigDecimal iexRealtimeSize;
private BigDecimal iexLastUpdated;
private BigDecimal delayedPrice;
private BigDecimal delayedPriceTime;
private BigDecimal extendedPrice;
private BigDecimal extendedChange;
private BigDecimal extendedChangePercent;
private BigDecimal extendedPriceTime;
private BigDecimal change;
private BigDecimal changePercent;
private BigDecimal iexMarketPercent;
private BigDecimal iexVolume;
private BigDecimal avgTotalVolume;
private BigDecimal iexBidPrice;
private BigDecimal iexBidSize;
private BigDecimal iexAskPrice;
private BigDecimal iexAskSize;
private BigDecimal marketCap;
private BigDecimal peRatio;
private BigDecimal week52High;
private BigDecimal week52Low;
private BigDecimal ytdChange;
public Quote(StockQuote quote) {
this.id = quote.getSymbol();
this.symbol = quote.getSymbol();
this.companyName = quote.getCompanyName();
this.primaryExchange = quote.getPrimaryExchange();
this.sector = quote.getSector();
this.calculationPrice = quote.getCalculationPrice();
this.open = quote.getOpen();
this.openTime = quote.getOpenTime();
this.close = quote.getClose();
this.closeTime = quote.getCloseTime();
this.high = quote.getHigh();
this.low = quote.getLow();
this.latestPrice = quote.getLatestPrice();
this.latestSource = quote.getLatestSource();
this.latestTime = quote.getLatestTime();
this.latestUpdate = quote.getLatestUpdate();
this.latestVolume = quote.getLatestVolume();
this.iexRealtimePrice = quote.getIexRealtimePrice();
this.iexRealtimeSize = quote.getIexRealtimeSize();
this.iexLastUpdated = quote.getIexLastUpdated();
this.delayedPrice = quote.getDelayedPrice();
this.delayedPriceTime = quote.getDelayedPriceTime();
this.extendedPrice = quote.getExtendedPrice();
this.extendedChange = quote.getExtendedChange();
this.extendedChangePercent = quote.getExtendedChangePercent();
this.extendedPriceTime = quote.getExtendedPriceTime();
this.change = quote.getChange();
this.changePercent = quote.getChangePercent();
this.iexMarketPercent = quote.getIexMarketPercent();
this.iexVolume = quote.getIexVolume();
this.avgTotalVolume = quote.getAvgTotalVolume();
this.iexBidPrice = quote.getIexBidPrice();
this.iexBidSize = quote.getIexBidSize();
this.iexAskPrice = quote.getIexAskPrice();
this.iexAskSize = quote.getIexAskSize();
this.marketCap = quote.getMarketCap();
this.peRatio = quote.getPeRatio();
this.week52High = quote.getWeek52High();
this.week52Low = quote.getWeek52Low();
this.ytdChange = quote.getYtdChange();
}
}
Build a CRUD Repository interface
package com.scotthensen.toolbox.scheduledtask.repository;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.scotthensen.toolbox.scheduledtask.cache.Quote;
@Repository
public interface QuoteRepository extends CrudRepository<Quote, String>{
Optional<Quote> findBySymbol(String symbol);
}
Modify the scheduled task to cache the quotes
package com.scotthensen.toolbox.scheduledtask;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.scotthensen.toolbox.scheduledtask.cache.Quote;
import com.scotthensen.toolbox.scheduledtask.domain.CompanyInfo;
import com.scotthensen.toolbox.scheduledtask.domain.StockQuote;
import com.scotthensen.toolbox.scheduledtask.repository.QuoteRepository;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class TaskGetFinancialUpdatesFromIEX<K,V> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss:SSS Z");
private static final String IEX_URL = "https://api.iextrading.com/1.0";
private static final String TESLA_QUOTE = "/stock/tsla/quote";
private static final String TESLA_COMPANY = "/stock/tsla/company";
@Autowired
private RedisTemplate<String, Object> template;
@Autowired
private QuoteRepository quoteRepository;
@Scheduled(fixedDelay = 10000)
public void getStockQuote() throws JsonProcessingException {
log.info("The time is now {}", dateFormat.format(new Date()), " ... start getStockQuote");
RestTemplate restTemplate = new RestTemplate();
StockQuote quote = restTemplate.getForObject(IEX_URL+TESLA_QUOTE, StockQuote.class);
log.info("\n>>> quote= " + quote.toString());
quoteRepository.save(new Quote(quote));
Optional<Quote> cachedQuote = quoteRepository.findBySymbol(quote.getSymbol());
cachedQuote.ifPresent(q -> log.info("\n>>> cached quote= " + q.toString()));
log.info("The time is now {}", dateFormat.format(new Date()), " ... stop getStockQuote");
}
@Scheduled(cron = "0/30 * * * * *")
public void getCompanyInfo() {
log.info("The time is now {}", dateFormat.format(new Date()), " ... start getStockQuote");
RestTemplate restTemplate = new RestTemplate();
CompanyInfo company = restTemplate.getForObject(IEX_URL+TESLA_COMPANY, CompanyInfo.class);
log.info(company.toString());
log.info("The time is now {}", dateFormat.format(new Date()), " ... stop getStockQuote");
}
}
Results - Spring Boot Console
Results - Redis Client Console
Credits
These two sites were very helpful in getting this working.