Spring Data项目是Spring用来解决数据访问问题的一揽子解决方案,Spring Data是一个伞形项目,包含了大量关系型数据库以及非关系型数据库的访问解决方案.Spring Data使我们可以快速且简单地使用普通的数据访问技术及新的数据访问技术
Spring Data为我们使用统一的API来对上述的数据存储技术进行数据访问操作提供了支持.这是Spring通过提供Spring Data Commons项目来实现的,它是上述各种Spring Data项目的依赖.Spring Data Commons让我们在使用关系型或者非关系型数据访问技术时都使用基于Spring的统一标准,该标准包含CRUD(创建、获取、更新、删除)、查询、排序和分页的相关的操作.
此处介绍下Spring Data Commons的一个重要概念:Spring Data Repository抽象.使用Spring Data Repository 可以极大地减少数据访问层的代码.既然是数据访问操作的统一标准,那么肯定是定义了各种数据和数据访问相关的接口,Spring Data Repository抽象的根接口是Repository接口:
public interface Repository<T, ID extends Serializable> {
}
从源码中可以看出,它接收领域类(JPA为实体类)和领域类的id类型作为参数类型.它的子接口CrudRepository定义了和CRUD操作相关的内容:
package org.springframework.data.repository;
import java.io.Serializable;
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> save(Iterable<S> var1);
T findOne(ID var1);
boolean exists(ID var1);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> var1);
long count();
void delete(ID var1);
void delete(T var1);
void delete(Iterable<? extends T> var1);
void deleteAll();
}
CrudRepository的子接口PagingAndSortingRepository定义了与分页和排序操作相关的内容:
package org.springframework.data.repository;
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort var1);
Page<T> findAll(Pageable var1);
}
不同的数据访问技术也提供了不同的Repository,如Spring Data有JpaRepository、Spring Data MongoDB有MongoRepository.
Spring Data项目还给我们提供了一个激动人心的功能,即可以根据属性名进行计数、删除、查询方法等操作,例如:
public interface PersonRepository extends Repository<Person,Long>{
//按照年龄计数
Long countByAge(Integer age);
//按照名字删除
Long deleteByName(String name);
//按照名字查询
List<Person> fingByName(String name);
//按照名字和地址查询
List<Person> findByNameAndAddress(String name,String address);
}
8.2 Spring Data JPA
8.2.1 点睛Spring Data JPA
1.什么是 Spring Data JPA
在介绍Spring Data JPA的时候,我们首先认识下Hibernate.Hibernate是数据访问解决技术的绝对霸主,在使用O/R映射(Object-Relational Mapping)技术实现数据访问,O/R映射即将领域模型类和数据库的表进行映射,通过程序操作对象而实现表数据操作的能力,让数据访问操作无须关注数据库相关的技术.
随着Hibernate的盛行,Hibernate主导了EJB3.0的JPA规范,JPA即 java persistence API.JPA是基于O/R映射的标准规范.所谓规范即只定义标准规则(如注解、接口)不提供实现,软件提供商可以按照标准规范来实现,而使用者只需要按照规范中来定义的方式使用,而不用和软件提供商的实现打交道,JPA的主要实现由Hibernate、EclipseLink和OpenJPA等,这也意味着我们只要使用JPA来开发,无论是哪一个开发方式都是一样的.
Spring Data JPA是Spring Data的一个子项目,它通过提供基于JPA的Repository的接口即可,定义如下:
public interface PersonRepository extends JpaRepository<Person,Long>{
//定义数据访问操作的方法
}
继承JpaRepository接口意味着我们默认已经有了数据访问操作方法
3.配置使用Spring Data JPA
在spring 环境中,使用Spring Data JPA可通过@EnableJpaRepositories注解来开启Spring Data JPA的支持,@EnableJpaRepositories接收的value参数用来扫描数据访问层所在包下的数据访问的接口定义.
@Configuration
@EnableJpaRepositories("com.wisely.repos")
public class JpaConfiguration{
@Bean
public EntityManagerFactory entityManagerFactory(){
//..
}
//还需要配置DataSource,PlatformTransactionManager等相关必须bean
}
4.定义查询方法
在讲解查询方法前,假设我们有一张数据表叫Person,有id(Number)、name(varchar2)、age(Number)、address(varchar2)几个字段;对应的实体类叫Person,分别有id(Long)、name(String)、age(Integer)、address(String).下面我们就以这个简单的实体查询作为演示.
(1)根据属性名查询
Spring Data JPA支持通过定义在Repository接口中的方法名来定义查询,而方法名是根据实体类的属性名来确定的.
1) 常规查询,根据属性名来定义查询方法,示例如下:
public interface PersonRepository extends JpaRepository<Person,Long>{
//通过名字相等查询,参数为name
//相当于JPQL:SELECT P FROM Person p where p.name=?1
List<Person> findByName(String name);
//通过名字like查询,参数为name
//相当于JPQL:select p from Person p where p.name like ?1
List<Person> findByNameLike(String name);
//通过名字和地址查询,参数为name和address
//相当于JPQL:select p from Person p where p.name=?1 and p.address=?2
List<Person> findByNameAndAddress(String name,String address);
}
从代码可以看出,这里使用了findBy、Like、And这样的关键字.其中findBy可以用find、read、readBy、query、queryBy、get、getBy来替代.
2) 限制结果数量.结果数量是用top和first关键字来实现的,例如:
public interface PersonRepository extends JpaRepository<Person,Long>{
//获得符合查询条件的前10条数据
List<Person> findFirst10ByName(String name);
//获得符合查询条件的前30条数据
List<Person> findTop30ByName(String name);
}
(2)使用JPA的NamedQuery查询
Spring Data JPA支持JPA的NameQuery来定义查询方法,即一个名称映射一个查询语句.定义如下:
@Entity
@NamedQuery(name="Person.findByName" ,query="select p from Person p where p.name=?1")
public class Person{
}
使用如下语句:
public interface PersonRepository extends JpaRepository<Person,Long>
//这时我们使用的是NamedQuery里定义的查询语句,而不是根据方法名称查询
List<Person> finByName(String name);
(3)使用@Query查询
1)使用参数索引.Spring Data JPA还支持用@Query注解在接口的方法上实现查询,例如
public interface PersonRepository extends JpaRepository<Person,Long>{
@Query("select p from Person p where p.address=?1")
List<Person> findByAddress(String address);
}
2)使用命名参数.上面的例子是使用参数的索引号来查询的,在Spring Data JPA里还支持在语句里用参数来匹配查询参数,例如:
public interface PersonRepository extends JpaRepository<Person,Long>{
@Query("select p from Person p where p.address= :address")
List<Person> findByAddress(@Param("address") String address);
}
3)更新查询.Spring Data JPA支持@Modifying和@Query注解组合来事件更新查询,例如:
public interface PersonRepository extends JpaRepository<Person,Long>{
@Modifying
@Transactional
@Query("update Person p set p.name=?1")
int setName(String name);
}
其中返回值int表示更新语句影响的行数
(4)Specification
JPA提供了基于准则查询的方式,即Criteria查询,而Spring Data JPA提供了一个specification(规范)接口让我们可以更方便地构造准则查询,Specification接口定义了一个toPredicate方法用来构造查询条件
1)定义.我们的接口类必须实现JpaSpecificationExecutor接口,代码如下:
public interface PersonRepository extends JpaRepository<Person,Long>,JpaSpecificationExecutor<Person>{
}
然后需要定义Criterial查询,代码如下:
public class CustomerSpecs{
public static Specification<Person> personFromHeFei(){
return new Specification<Person>{
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
return cb.equal(root.get("addres"),"合肥");
}
}
}
}
我们使用Root来获得需要查询的属性,通过CriteriaBuilder构造查询条件,本例的含义是查出来所有来自合肥的人.
注意:CriteriaQuery、CriteriaBuilder 、Predicate 、Root都来自JPA的接口.
CriteriaBuilder 包含的条件构造有:exists、and、or、not、conjunction、disjunction、isTrue、isFalse、isNull、isNotNull、equal、notEqual、greaterThan、greaterThanOrEqualTo、lessThan、lessThanOrEqualTo、between等,详细请查看CriteriaBuilder 的API.
2)注入personRepository的Bean后:
List<Person> people = PersonRepository.findAll(personFromHeFei());
(5)排序与分页
Spring Data JPA充分考虑了在实际开发中所需要的排序和分页的场景,为我们提供了Sort类以及Page接口和Pageable接口.
1)定义:
public interface PersonRepository extends JpaRepository<Person,Long>{
List<Person> findByName(String name,Sort sort);
Page<Person> findByName(String name,Pageable pageable);
}
2)使用排序:
List<Person> people = personRepository.findByName("xx",new Sort(Direction.ASC,"age"));
3)使用分页:
Page<Person> people = personRepository.findByName("xx",new PageRequest(0,10));
其中Page接口可以获得当前页面的记录、总页数、总记录数、是否有上一页或下一页等.
5.自定义Repsoitory的实现
Spring Data提供了和CrudRepository、PagingAndSortingRepository;Spring Data JPA也提供了JpaRepository.如果我们想把自己常用的数据库操作封装起来,像JpaRepository一样提供给我们领域类的Repository接口使用,应该怎么操作呢?
(1)定义自定义Repository接口:
@NoRepositoryBean//1
public interface CustomRepository<T,ID extends Serializable> extends PagingAndSortingRepository<T,ID>{//2
public void doSomething(ID id);//3
}
代码解释
①:@NoRepositoryBean指明当前这个接口不是我们领域类的接口(如PersonRepository)
②:我们自定义的Repository实现PagingAndSortingRepository接口,具备分页和排序的能力.
③:要定义的数据操作方法在接口中的定义
(2)定义接口实现:
public class CustomRepositoryImpl<T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements CustomRepository<T,ID>{//1
private final EntityManager entityManager;//2
public CustomRepositoryImpl(Class<T> domainClass,EntityManager entityManager){//3
super(domainClass,entityManager)
this.entityManager = entityManager;
}
@Override
public void doSomething(ID id){
//4
}
}
代码解释
①:首先要实现CustomRepository接口,继承SimpleJpaRepository类让我们可以使用其提供的方法(如finaAll).
②:让数据造作方法中可以使用entityManager.
③:CustomRepositoryImpl的构造函数,需要当前处理的领域类型和entityManager作为构造参数,在这里也给我们的entityManager赋值了.
④:在此处定义数据访问操作,如调用findAll方法并构造一些查询条件
(3)自定义RepositoryFactoryBean.自定义JpaRepositoryFactoryBean替代默认RepositoryFactoryBean,我们会获得一个RepositoryFactory,RepositoryFactory将会注册我们自定义的Repository的实现:
public class CustomRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable>
extends JpaRepositoryFactoryBean<T, S, ID> {// 1
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {// 2
return new CustomRepositoryFactory(entityManager);
}
private static class CustomRepositoryFactory extends JpaRepositoryFactory {// 3
public CustomRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
@Override
@SuppressWarnings({"unchecked"})
protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(
RepositoryInformation information, EntityManager entityManager) {// 4
return new CustomRepositoryImpl<T, ID>((Class<T>) information.getDomainType(), entityManager);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {// 5
return CustomRepositoryImpl.class;
}
}
}
代码解释:
①:自定义RepositoryFactoryBean,继承JpaRepositoryFactoryBean.
②:重写createRepositoryFactory方法,用当前的CustomRepositoryFactory创建实例
③:创建CustomRepositoryFactory,并继承JpaRepositoryFactory
④:重写getTargetRepository,获得当前自定义的Repository实现
⑤:重写getRepositoryBaseClass,获得当前自定义的Repository实现的类型
(4)开启自定义支持使用@EnableJpaRepositories的repositoryFactoryBeanClass来指定FactoryBean即可,代码如下
@EnableJpaRepositories(repositoryFactoryBeanClass=CustomRepositoryFactoryBean.class)