2019独角兽企业重金招聘Python工程师标准>>>
Spring Data 项目的目的是为了简化构建基于 Spring 框架应用的数据访问计数,包括非关系数据库、Map-Reduce 框架、云数据服务等等;另外也包含对关系数据库的访问支持。
目的
本篇博文的主要目的是使用Spring Data Redis的CrudRepository接口,完成对Redis的基本操作,如保存、删除、查看是否存在、查询全部、分页查询等。
环境准备
添加依赖
本文采用是Maven工程来构建工程,所包括的依赖有spring-data-redis,详细信息如下:
org.springframework.data
spring-data-redis
1.8.0.RELEASE
redis.clients
jedis
2.9.0
Redis配置类
package com.xxx.springdata.redis.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
@Configuration
@ComponentScan("com.xxx.springdata")
@PropertySource("classpath:redis.properties")
@EnableRedisRepositories("com.xxx.springdata")
public class RedisAppConfig {
@Value("${redis.host}")
private String redisHost;
@Value("${redis.port}")
private int redisPort;
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisHost);
factory.setPort(redisPort);
factory.setUsePool(true);
return factory;
}
@Bean
RedisTemplate, ?> redisTemplate() {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
@Bean
CacheManager cacheManager() {
return new RedisCacheManager(redisTemplate());
}
}
其中redis.host 和 redis.port来源于src/main/resources目录下的redis.properties文件
redis.host=172.xx.xx.xx
redis.port=6379
Book类
本文使用Book类来完成Spring Data Redis的操作,Book类包含id, name, isbn 以及authors, 分别表示Book Id, 书名,ISBN以及书本作者,其它信息如出版信息等都不在这个示例中。
/** Book ID */
private Long id;
/** 书名 */
private String name;
/** ISBN */
private String isbn;
/** 作者 */
private List authors;
详细的代码如下:
package com.xxx.springdata.redis.entity;
import java.io.Serializable;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
@RedisHash("books")
public class Book implements Serializable {
private static final long serialVersionUID = 4665407921597036726L;
/** Book ID */
@Id
private Long id;
/** 书名 */
@Indexed
private String name;
/** ISBN */
private String isbn;
/** 作者 */
private List authors;
public Book() {
}
public Book(Long id, String name, String isbn, List authors) {
this.id = id;
this.name = name;
this.isbn = isbn;
this.authors = authors;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name
* the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the isbn
*/
public String getIsbn() {
return isbn;
}
/**
* @param isbn
* the isbn to set
*/
public void setIsbn(String isbn) {
this.isbn = isbn;
}
/**
* @return the authors
*/
public List getAuthors() {
return authors;
}
/**
* @param authors
* the authors to set
*/
public void setAuthors(List authors) {
this.authors = authors;
}
/**
* @return the id
*/
public Long getId() {
return id;
}
/**
* @param id
* the id to set
*/
public void setId(Long id) {
this.id = id;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Book [id=" + id + ", name=" + name + ", isbn=" + isbn + ", authors=" + authors + "]";
}
}
BookRepository
本文开始就说明了本文的目的:
使用Spring Data Redis的CrudRepository接口,完成对Redis的基本操作,如保存、删除、查看是否存在、查询全部、分页查询等。
所以,在编写BookRepository接口之前,我们先来看一下org.springframework.data.repository包下的CrudRepository已经为我们提供了哪些方法,其源代码如下:
@NoRepositoryBean
public interface CrudRepository extends Repository {
/**
* Saves a given entity. Use the returned instance for further operations as the save operation might have changed the
* entity instance completely.
*
* @param entity
* @return the saved entity
*/
S save(S entity);
/**
* Saves all given entities.
*
* @param entities
* @return the saved entities
* @throws IllegalArgumentException in case the given entity is {@literal null}.
*/
Iterable save(Iterable entities);
/**
* Retrieves an entity by its id.
*
* @param id must not be {@literal null}.
* @return the entity with the given id or {@literal null} if none found
* @throws IllegalArgumentException if {@code id} is {@literal null}
*/
T findOne(ID id);
/**
* Returns whether an entity with the given id exists.
*
* @param id must not be {@literal null}.
* @return true if an entity with the given id exists, {@literal false} otherwise
* @throws IllegalArgumentException if {@code id} is {@literal null}
*/
boolean exists(ID id);
/**
* Returns all instances of the type.
*
* @return all entities
*/
Iterable findAll();
/**
* Returns all instances of the type with the given IDs.
*
* @param ids
* @return
*/
Iterable findAll(Iterable ids);
/**
* Returns the number of entities available.
*
* @return the number of entities
*/
long count();
/**
* Deletes the entity with the given id.
*
* @param id must not be {@literal null}.
* @throws IllegalArgumentException in case the given {@code id} is {@literal null}
*/
void delete(ID id);
/**
* Deletes a given entity.
*
* @param entity
* @throws IllegalArgumentException in case the given entity is {@literal null}.
*/
void delete(T entity);
/**
* Deletes the given entities.
*
* @param entities
* @throws IllegalArgumentException in case the given {@link Iterable} is {@literal null}.
*/
void delete(Iterable extends T> entities);
/**
* Deletes all entities managed by the repository.
*/
void deleteAll();
}
从上述CrudRepository接口的源码中,我们可以看到它已经提供了诸如save(),delete(),findOne(),findAll(),count()等基本的方法。
我们主要简单地继承CrudRepository接口,就能拥有其定义的基本方法,如:
public interface BookRepository extends CrudRepository {
}
经过上述简单的定义,BookRepository 就可以拥有count(), delete(), findAll() ~~~等等的方法,大大简化了代码。BookRepository实例拥有的方法如下图所示:
因为要测试分页的功能,所以在BookRepository中添加了一个根据书名分页查询的方法:
Page
findBookByName(String name, Pageable page);
package com.xxx.springdata.redis.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import com.xxx.springdata.redis.entity.Book;
public interface BookRepository extends CrudRepository {
Page findBookByName(String name, Pageable page);
}
BookService
因为只是个示例,BookService直接就使用实现了,而不是采用接口和实现分开的方式来做。
package com.xxx.springdata.redis.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import com.xxx.springdata.redis.entity.Book;
import com.xxx.springdata.redis.repository.BookRepository;
@Service
public class BookService {
@Autowired
private BookRepository bookRepository;
public void save(Book book) {
bookRepository.save(book);
}
public void deleteById(Long id) {
bookRepository.delete(id);
}
public void saveByBatch(List books) {
bookRepository.save(books);
}
public Book findOne(Long id) {
return bookRepository.findOne(id);
}
public Page findBookByName(String name, int pageNo, int pageSize) {
return bookRepository.findBookByName(name, new PageRequest(pageNo - 1, pageSize));
}
public Iterable findAll() {
return bookRepository.findAll();
}
public long count() {
return bookRepository.count();
}
}
从上述代码中,我们可以看出:
只要在BookService中自动注入 (使用@Autowired注解) BookRepository,就可以根据bookRepository 实例完成基本的业务方法操作。
准备好这些之后,我们就可以开启我们的示例编写了~~~Let's Go~~
示例
获取BookService实例
ApplicationContext context = new AnnotationConfigApplicationContext(RedisAppConfig.class);
BookService bookService = context.getBean(BookService.class);
单个保存
/**
* 保存Book
*/
Book book = new Book();
book.setId(10001L);
book.setAuthors(Arrays.asList("Eric", "Joan"));
book.setIsbn("abc-123-def-456789");
book.setName("Redis Examples");
bookService.save(book);
根据ID查询
/**
* 根据Id查询
*/
// Book [id=10001, name=Redis Examples, isbn=abc-123-def-456789,
// authors=[Eric, Joan]]
Book selectBookFromRedis = bookService.findOne(book.getId());
System.out.println(selectBookFromRedis);
根据ID删除
/**
* 删除
*/
System.out.println("删除单个Book");
bookService.deleteById(book.getId());
批量保存
Book book2 = new Book();
book2.setId(10002L);
book2.setAuthors(Arrays.asList("Eric", "Joan"));
book2.setIsbn("abc-123-def-456789");
book2.setName("Redis Examples");
System.out.println("批量保存");
bookService.saveByBatch(Arrays.asList(book, book2));
获取全部
/**
* 获取所有Books
*/
System.out.println("获取所有Books");
Iterable iterableAllBooks = bookService.findAll();
Iterator iterAllBooks = iterableAllBooks.iterator();
while(iterAllBooks.hasNext()) {
Book item = iterAllBooks.next();
System.out.println(item);
}
分页
本文使用按照名字查询到的结果来分页,分页的实现包含如下几个步骤:
- 步骤一
在BookRepository定义一个方法,其中使用Pagable作为参数,如:
public interface BookRepository extends CrudRepository {
Page findBookByName(String name, Pageable page);
}
- 步骤二
在BookService中,实现分页代码,包含传递的页码(pageNo)以及每页查询的数字,如:
public Page findBookByName(String name, int pageNo, int pageSize) {
return bookRepository.findBookByName(name, new PageRequest(pageNo - 1, pageSize));
}
说明:
因为PageRequest中的page下标是从0开始的,所以这边使用了pageNo-1,其中,
org.springframework.data.domain包下的PageRequest构造函数如下:
/**
* Creates a new {@link PageRequest}. Pages are zero indexed, thus providing 0 for {@code page} will return the first
* page.
*
* @param page zero-based page index.
* @param size the size of the page to be returned.
*/
public PageRequest(int page, int size) {
this(page, size, null);
}
- 步骤三
使用BookService实例完成相应的测试代码。
/**
* 分页
*/
/**
* 第一页
*/
System.out.println("第一页的内容");
Page page1Books = bookService.findBookByName("Redis Examples", 1, 1);
Iterator iter1 = page1Books.iterator();
while (iter1.hasNext()) {
Book item = iter1.next();
System.out.println(item);
}
/**
* 第二页
*/
System.out.println("第二页的内容");
Page page2Books = bookService.findBookByName("Redis Examples", 2, 1);
Iterator iter2 = page2Books.iterator();
while (iter2.hasNext()) {
Book item = iter2.next();
System.out.println(item);
}
count
/**
* 获取存储的Book总数
*/
long count = bookService.count();
System.out.println("Count is " + count);
完整示例
完整的RedisExample.java代码如下:
package com.xxx.springdata.example;
import java.util.Arrays;
import java.util.Iterator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.data.domain.Page;
import com.xxx.springdata.redis.config.RedisAppConfig;
import com.xxx.springdata.redis.entity.Book;
import com.xxx.springdata.redis.service.BookService;
public class RedisExample {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(RedisAppConfig.class);
BookService bookService = context.getBean(BookService.class);
/**
* 保存Book
*/
Book book = new Book();
book.setId(10001L);
book.setAuthors(Arrays.asList("Eric", "Joan"));
book.setIsbn("abc-123-def-456789");
book.setName("Redis Examples");
bookService.save(book);
/**
* 根据Id查询
*/
// Book [id=10001, name=Redis Examples, isbn=abc-123-def-456789,
// authors=[Eric, Joan]]
Book selectBookFromRedis = bookService.findOne(book.getId());
System.out.println(selectBookFromRedis);
/**
* 删除
*/
System.out.println("删除单个Book");
bookService.deleteById(book.getId());
Book book2 = new Book();
book2.setId(10002L);
book2.setAuthors(Arrays.asList("Eric", "Joan"));
book2.setIsbn("abc-123-def-456789");
book2.setName("Redis Examples");
System.out.println("批量保存");
bookService.saveByBatch(Arrays.asList(book, book2));
/**
* 获取所有Books
*/
System.out.println("获取所有Books");
Iterable iterableAllBooks = bookService.findAll();
Iterator iterAllBooks = iterableAllBooks.iterator();
while(iterAllBooks.hasNext()) {
Book item = iterAllBooks.next();
System.out.println(item);
}
/**
* 分页
*/
/**
* 第一页
*/
System.out.println("第一页的内容");
Page page1Books = bookService.findBookByName("Redis Examples", 1, 1);
Iterator iter1 = page1Books.iterator();
while (iter1.hasNext()) {
Book item = iter1.next();
System.out.println(item);
}
/**
* 第二页
*/
System.out.println("第二页的内容");
Page page2Books = bookService.findBookByName("Redis Examples", 2, 1);
Iterator iter2 = page2Books.iterator();
while (iter2.hasNext()) {
Book item = iter2.next();
System.out.println(item);
}
/**
* 获取存储的Book总数
*/
long count = bookService.count();
System.out.println("Count is " + count);
}
}
控制台输出如下内容:
Book [id=10001, name=Redis Examples, isbn=abc-123-def-456789, authors=[Eric, Joan]]
删除单个Book
批量保存
获取所有Books
Book [id=10001, name=Redis Examples, isbn=abc-123-def-456789, authors=[Eric, Joan]]
Book [id=10002, name=Redis Examples, isbn=abc-123-def-456789, authors=[Eric, Joan]]
第一页的内容
Book [id=10001, name=Redis Examples, isbn=abc-123-def-456789, authors=[Eric, Joan]]
第二页的内容
Book [id=10002, name=Redis Examples, isbn=abc-123-def-456789, authors=[Eric, Joan]]
Count is 2
可以通过Redis Desktop Manager工具来查看Redis中存储的内容,如id为10001的book信息如:
至此,使用Spring Data Redis 的 CrudRepository完成基本的操作示例就完成了。
但是,在使用的过程中也遇到了一些问题,在这里也将遇到的几个问题和相应的解决方案记录下来,可以为可能碰到类似问题的读者提供一个参考。
问题及解决
问题一
最开始的时候,Book.java的内容如下:
package com.xxx.springdata.redis.entity;
import java.io.Serializable;
import java.util.List;
public class Book implements Serializable {
private static final long serialVersionUID = 4665407921597036726L;
/** Book ID */
private Long id;
/** 书名 */
private String name;
/** ISBN */
private String isbn;
/** 作者 */
private List authors;
public Book() {
}
public Book(Long id, String name, String isbn, List authors) {
this.id = id;
this.name = name;
this.isbn = isbn;
this.authors = authors;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name
* the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the isbn
*/
public String getIsbn() {
return isbn;
}
/**
* @param isbn
* the isbn to set
*/
public void setIsbn(String isbn) {
this.isbn = isbn;
}
/**
* @return the authors
*/
public List getAuthors() {
return authors;
}
/**
* @param authors
* the authors to set
*/
public void setAuthors(List authors) {
this.authors = authors;
}
/**
* @return the id
*/
public Long getId() {
return id;
}
/**
* @param id
* the id to set
*/
public void setId(Long id) {
this.id = id;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Book [id=" + id + ", name=" + name + ", isbn=" + isbn + ", authors=" + authors + "]";
}
}
尝试保存Book到Redis的时候报错:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xxx.springdata.redis.repository.BookRepository' available:
详细异常信息如下:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bookService': Unsatisfied dependency expressed through field 'bookRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xxx.springdata.redis.repository.BookRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.(AnnotationConfigApplicationContext.java:84)
at com.xxx.springdata.example.RedisExample.main(RedisExample.java:18)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xxx.springdata.redis.repository.BookRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1486)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 14 more
解决方法
光看异常信息,可能会误解为哪里配置有误,导致BookRepository自动注入失败,其实不然。
解决该问题,我们可以为Book添加@RedisHash注解即可。
其中,RedisHash注解为org.springframework.data.redis.core.RedisHash
@RedisHash("books")
public class Book implements Serializable {
问题二
按照书名查询的分页结果,没有任何返回信息。
解决方法
为Book类中的name属性添加一个@Indexed注解即可。
其中,Indexed注解为 org.springframework.data.redis.core.index.Indexed
/** 书名 */
@Indexed
private String name;
小结
至此,使用Spring Data Redis的CrudRepository的简单示例就完成了~~~
结合使用中遇到的问题,我们可以通过如下几个步骤,来确保使用Spring Data Redis的正确姿势
配置Redis中使用@EnableRedisRepositories
实体类使用@RedisHash等注解
如果需要按照某个属性,如本例的name,来完成查询,使用@Indexed注解标识
可以使用@Id来表示唯一属性
大家如有什么问题或者建议,一起来分享吧~~~