Spring Data的CrudRepository使用及其遇到的问题和解决方法

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

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
		

Spring Data的CrudRepository使用及其遇到的问题和解决方法_第1张图片

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.hostredis.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 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实例拥有的方法如下图所示:

Spring Data的CrudRepository使用及其遇到的问题和解决方法_第2张图片

因为要测试分页的功能,所以在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的CrudRepository使用及其遇到的问题和解决方法_第3张图片

至此,使用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来表示唯一属性

大家如有什么问题或者建议,一起来分享吧~~~

转载于:https://my.oschina.net/wangmengjun/blog/833916

你可能感兴趣的:(Spring Data的CrudRepository使用及其遇到的问题和解决方法)