Java for Web学习笔记(一一三):Spring Data(1)何为Spring Data

CRUD并不足够

在之前学习中,使用了通用仓库接口GenericRepository,并通过GenericBaseRepository,GenericJpaRepository来具体实现,实现上对于CRUD而言,这类的实现大同小异,此外,我们还需要根据UNIQUE KEY或者KEY去进行查询,而不仅仅使用primary key,还有根据多个条件去进行查询或者检索,对返回的结果进行分页等等。GenericRepository提供的基础CRUD是不足够的。

这些接口的实现是比较繁琐的,我们以分页为例。如果我们需要获取能够分多少页,就需要获取总数,代码如下:

@PersistenceContext private EntityManager entityManager;

@Override
public Long count(Predicate ... restrictions) {
	CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();		
	CriteriaQuery<Long> countCriteria = builder.createQuery(Long.class);
	Root<TestPage> root = countCriteria.from(TestPage.class);
	TypedQuery<Long> countQuery = this.entityManager.createQuery(
				countCriteria.select(builder.count(root))
				.where(restrictions));	
	return countQuery.getSingleResult();
}

@Override
public List<TestPage> page(int page, int numPerPage, Predicate ... restrictions) {
	CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
	CriteriaQuery<TestPage> criteria = builder.createQuery(TestPage.class);
	Root<TestPage> root = criteria.from(TestPage.class);
	TypedQuery<TestPage> pagedQuery = this.entityManager.createQuery(
				criteria.select(root)
				.orderBy(builder.asc(root.get("userName")))
				.where(restrictions));	
	//【注意】setFristResult:position of the first result, numbered from 0,这个自动分配的id号从1开始不一样
	return pagedQuery.setFirstResult((page -1) *numPerPage).setMaxResults(numPerPage).getResultList();
}

我们可以将这两个方法写成通用的代码加入到GenericJpaRepository中,但在获取分页的信息时,可能指定某种或者某些排序,也可以需要进行过滤,也就是在例子中的where(restrictions),这使得调用方陷入到JPA的代码中,如果根据具体场景具体化,可能需要为很多Entity的仓库添加代码,无法做到通用。

Spring Data的作用

仓库的代码不包含任何的业务逻辑(在Service),也不包含UI(在Controller),是可以通用的样板代码。Spring Data它是独立于Spring framework的一个项目。程序员只需定义接口,Spring Data在runtime时可以动态生成所需的仓库代码。


Spring Data支持JPA,JdbcTemplate,NoSQL等,包含所有这些是Spring Data Commons,我们也可以只选择实现JAP的部分,即Spring Data JPA。

简单小例子

Pom的引入

<!--  在我们的小实验中,spring-data-jpa不能使用2的版本,注入出现异常 -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.11.10.RELEASE<version>
</dependency>

如果我们希望使用spring data jpa 2的版本(例如2.0.5.RELEASE),就需要升级spring framework至5.x,而spring framework 5.x要求javax.servlet是4.0的。

在上下文配置Spring data

只需加上@EnableJpaRepositories

@EnableJpaRepositories("cn.wei.flowingflying.chapter22.site.repositories")
/* 如果我们在entityManagerFactory和transactionManager中没有使用默认的方法名,需要明确指出
 * @EnableJpaRepositories( basePackages = "cn.wei.flowingflying.chapter22.site.repositories"
 *                         entityManagerFactoryRef = "entityManagerFactoryBean", //缺省为entityManagerFactory
 *                         transactionManagerRef = "jpaTransactionManager" ) //缺省为transactionManager
 */
@ComponentScan(
    basePackages = "cn.wei.flowingflying.chapter22.site",
    excludeFilters = @ComponentScan.Filter({Controller.class, ControllerAdvice.class})
)
public class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer{
    ... ...
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws PropertyVetoException{
        Map<String, Object> properties = new Hashtable<>();
        properties.put("javax.persistence.schema-generation.database.action","none");
        properties.put("hibernate.show_sql", "true");
        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
	
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        factory.setDataSource(this.springJpaDataSource());
        factory.setPackagesToScan("cn.wei.flowingflying.chapter22.site.entity");
        factory.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
        factory.setValidationMode(ValidationMode.NONE);
        factory.setJpaPropertyMap(properties);		
        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() throws PropertyVetoException{	
        return new JpaTransactionManager(this.entityManagerFactory().getObject());
    }
}

仓库接口

既然Spring data可以为我们自动生成代码,那么如何写仓库的接口就成为关键。org.springframework.data.repository.Repository<T, ID extends Serializable>是Spring Data中最基础的接口,没有任何方法,其他的接口都是继承它。T就是Entity的类,ID就是主键的类型。

/* CrudRepository是Spring Data提供的接口增删改查的仓库接口,我们只需继承即可,它将提供下面的接口
 * ➤ count()返回long,就是total number
 * ➤ delete(T), delete(ID), delete(Iterable<? extends T>), deleteAll()
 * ➤ exists(ID)查询ID是否存在,返回boolean
 * ➤ findAll(),findAll(Iterable<ID>),均返回Iterable<T>
 * ➤ findOne(ID)
 * ➤ save(S),其中S是T的继承,可能执行insert,也可能执行update操作,返回S(如果是insert,我们可以从中获取自动分配的ID)
 * ➤ save(Iterable<S>),返回Iterable<S>,同样S是T的继承
 */
public interface AuthorRepository extends CrudRepository<Author,Long>{

}

如果我们需要排序或者分页,就要继承org.springframework.data.repository.PagingAndSortingRepository<T, ID extendsSerializable>,提供了:

@Service
public class DefaultBookManager implements BookManager {
	@Inject AuthorRepository authorRepository;
	
	@Override
	@Transactional
	public List<Author> getAuthors() {
//		return this.toList(authorRepository.getAll()); //以前的代码
		return this.toList(authorRepository.findAll());		
	}

	@Override
	@Transactional
	public void saveAuthor(Author author) {
		/*  以前的代码
		if(author.getId() < 1)
			this.authorRepository.add(author);
		else
			this.authorRepository.update(author); */
		this.authorRepository.save(author);		
	}
	
	/* 【问题】为何仓库返回的是一个Iterable<E>,我们要转换为Collection,如List?
         * 在原生的JDBC镇南关,繁华的是ResultSet,是Iterable,这使得我们可以执行语句,同时继续从数据库中读取信息。
         * 在事务方法中,我们已经commit了事务,关闭了ResultSet,转换为list这类collection,确保所有的数据在事务结束前已经被读取。
         */
	private <E> List<E> toList(Iterable<E> i){
		List<E> list = new ArrayList<>();
		i.forEach(list::add);
		return list;
	}
	... ...
}
  • findAll(Sort)
  • findAll(Pageable)

使用仓库接口

@Service
public class DefaultBookManager implements BookManager {
	@Inject AuthorRepository authorRepository;
	
	@Override
	@Transactional
	public List<Author> getAuthors() {
//		return this.toList(authorRepository.getAll()); //以前的代码
		return this.toList(authorRepository.findAll());		
	}

	@Override
	@Transactional
	public void saveAuthor(Author author) {
		/*  以前的代码
		if(author.getId() < 1)
			this.authorRepository.add(author);
		else
			this.authorRepository.update(author); */
		this.authorRepository.save(author);		
	}
	
	/* 【问题】为何仓库返回的是一个Iterable<E>,我们要转换为Collection,如List?
         * 在原生的JDBC镇南关,繁华的是ResultSet,是Iterable,这使得我们可以执行语句,同时继续从数据库中读取信息。
         * 在事务方法中,我们已经commit了事务,关闭了ResultSet,转换为list这类collection,确保所有的数据在事务结束前已经被读取。
         */
	private <E> List<E> toList(Iterable<E> i){
		List<E> list = new ArrayList<>();
		i.forEach(list::add);
		return list;
	}
	... ...
}

相关链接: 我的Professional Java for Web Applications相关文章


猜你喜欢

转载自blog.csdn.net/flowingflying/article/details/79515048