spring bootでredisにキャッシュする際のcache abstractionについて書くページ。 cache abstractionはキャッシュの抽象化が直訳だが、日本語では透過的キャッシュという言葉も使うらしい。Spirng4.1からJSR-107のキャッシュ用のアノテーションがフルサポートされている。
はじめに Spring bootでSpring SessionとSpring Redisを使えば簡単にSessionをRedisに保存できるが、Session情報以外にもアプリケーションから高頻度で参照されるデータなどはRedis(メモリ)に置きたくなる。
RDBもキャッシュの仕組みがあるが、メモリにすべてのデータが乗っているわけではないのでメモリに無いデータはディスクから読むこととなる。対して、Redisの場合はすべてのデータがメモリにあるのでRedisのほうが高速にデータを取り出すことができる。
Spring boot 1.4.Xを使えばDBに保存されているデータをRedisにキャッシュする処理が記述できる。意外と簡単にできたのでこのページではその方法を記述する。
書くこと DBに保存されているデータが初めてServiceなどから参照された時にそのデータをRedisにキャッシュして次回からはキャッシュされたRedis上のデータを参照する様に設定する。 要はデータがDBにあるのかキャッシュにあるかを意識せずにプログラミングすることができるようになります。
設定 ソースはこちら
dependency
pom.xml
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <scope > runtime</scope > </dependency >
application.yml MySQLとRedisは既にインストールしてあるものとする。
spring: redis: host: localhost port: 6379 datasource: url: jdbc:mysql://localhost:3306/sample_db username: root password: password driver-class-name: com.mysql.jdbc.Driver
tableとEntitiy Cacheするデータが入ったテーブルは以下。すご~く適当に作成。mysql> select * from users; +----+-------+------------------+----------+-----------+---------+----------+ | id | name | email | username | authority | enabled | password | +----+-------+------------------+----------+-----------+---------+----------+ | 1 | ishii | ishii@ishii.tech | | NULL | NULL | NULL | +----+-------+------------------+----------+-----------+---------+----------+
上記のテーブルに対応するEntityクラス。
@Data @Entity @Table (name = "users" )public class User implements Serializable { @Id @GeneratedValue (strategy= GenerationType.AUTO) private long id; private String name; private String email; private String username; private Boolean enabled; private String authority; private String password; }
repository Userを取り出すRepositoryは以下。適当なメソッドを書く。
@Repository public interface UserRepository extends JpaRepository <User , Long > { public User findByName (String name) ; public User findByEmail (String email) ; }
Redis設定とService ここからRedisへキャッシュするための設定を行う。
AutoConfigで設定されるデフォルトのSerializerはKeyに対してもJdkSerializationRedisSerializerが適用されてすごくわかりづらい。Java以外からも参照したい場合はStringRedisSerializerなどを適用して文字列としてRedisに保存するようにしておいたほうがいい。ここではKeyは文字列を適用している。
JedisConnectionFactoryでコネクションを生成しているが、RedisConnectionFactoryも使える。特に困ったことがないので違いを調べていないがここではJedisConnectionFactoryを使っている。
Json形式も扱えるがJsonの型がEntity毎にhogehogeJsonRedisTemplateみたいなものを作成する必要があるっぽい。とりあえずここでは文字列で保存するのでJsonの話はしない。
@Configuration @EnableCaching public class RedisConfig { @Autowired private JedisConnectionFactory jedisConnectionFactory; @Bean public RedisTemplate redisTemplate () { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(jedisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Bean public RedisCacheManager redisCacheManager () { return new RedisCacheManager(redisTemplate()); } }
戻り値をキャッシュしたいメソッドに@Cacheable
をつけることでRedisにキャッシュがなければDBからデータを取得してRedisにキャッシュすることができる。
@Service @Transactional public class UserService { @Autowired UserRepository userRepository; @Cacheable (cacheNames = "User" , key = "'User:' + #name" ) public User findByName (String name) { return userRepository.findByName(name); } public User findByEmail (String email) { return userRepository.findByEmail(email); } }
@Cacheable
はいくつかパラメータが設定できる。パラメータの詳細はこちら。
ここではキャッシュの名前としてUser、キャッシュのKeyを引数のnameとして設定している。Keyの設定にはSpELが使用できる。
Controller 上で作ったServiceを実行するために適当なControllerを作る。
@Controller public class UserController { @Autowired UserService userService; @RequestMapping (value = "/" ) @ResponseBody public String list () { User user = userService.findByName("ishii" ); return user.getName(); } }
確認 Controllerに接続してRedisとDBに対するアクセスを確認してみる。
MySQLのクエリログとRedisのmonitorを表示しながら確認を行った。 書いてから思ったのですが、タイムスタンプの比較がわかりにくい。
$ tail -f /var/log/query.log 160902 1:02:59 2 Query SET autocommit=0 2 Query select user0_.id as id1_0_, user0_.authority as authorit2_0_, user0_.email as email3_0_, user0_.enabled as enabled4_0_, user0_.name as name5_0_, user0_.password as password6_0_, user0_.username as username7_0_ from users user0_ where user0_.name='ishii' 2 Query commit 2 Query SET autocommit=1
$ redis-cli monitor 1472745767.071485 [0 127.0.0.1:39530] "flushall" 1472745779.411708 [0 127.0.0.1:40090] "PING" 1472745779.540049 [0 127.0.0.1:40090] "EXISTS" "User~lock" 1472745779.541225 [0 127.0.0.1:40090] "GET" "User:ishii" 1472745779.548405 [0 127.0.0.1:40090] "EXISTS" "User~lock" 1472745779.549486 [0 127.0.0.1:40090] "MULTI" 1472745779.549496 [0 127.0.0.1:40090] "SET" "User:ishii" "\xac\xed\x00\x05sr\x00\x17com.example.entity.User\r\xd5D]w\xbbXX\x02\x00\aJ\x00\x02idL\x00\tauthorityt\x00\x12Ljava/lang/String;L\x00\x05emailq\x00~\x00\x01L\x00\aenabledt\x00\x13Ljava/lang/Boolean;L\x00\x04nameq\x00~\x00\x01L\x00\bpasswordq\x00~\x00\x01L\x00\busernameq\x00~\x00\x01xp\x00\x00\x00\x00\x00\x00\x00\x01pt\x00\x10ishii@ishii.techpt\x00\x05ishiipt\x00\x00" 1472745779.549526 [0 127.0.0.1:40090] "ZADD" "User~keys" "0.0" "User:ishii" 1472745779.549537 [0 127.0.0.1:40090] "EXEC" 1472745785.782579 [0 127.0.0.1:40090] "EXISTS" "User~lock" 1472745785.784189 [0 127.0.0.1:40090] "GET" "User:ishii" 1472745809.412183 [0 127.0.0.1:40090] "PING"
Controllerに初めてアクセスした時はキャッシュにデータが無いのでDBへの問い合わせが行われるが、2回目以降はRedisからデータが読み出され、DBへの問い合わせは行われなくなる。
まとめ アノテーション1つで簡単にキャッシュの操作ができることが確認できた。読み出しのみ確認したが、削除と更新についてもアノテーションを付与するだけで簡単にキャッシュの操作ができるようになっている。
@CacheEvict
が削除、@CachePut
が更新で同じようにServiceの該当するメソッドに付与することでキャッシュの操作ができる。
おわり。
参考
https://spring.io/guides/gs/caching/
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html
http://blog.rakugakibox.net/entry/2015/07/27/spring-boot-with-redis
http://qiita.com/yoshidan/items/f7c10a43d2a40c3ce8df