SpringData之JPA
SpringData为我们使用统一的API来对主流的数据存储技术进行数据访问操作提供了支持(基于JPA标准)。这是Spring通过SpringDataCommons项目来实现的,它是上述各种SpringData项目的依赖。SpringDataCommons让我们在使用关系型或非关系型数据访问技术时都使用基于Spring的统一标注,该标准包含CRUD(创建、获取、更新、删除)查询、排序和分页的相关操作。
SpringDataCommons
SpringDataCommons是SpringDataRepository抽象。使用SpringDataRepository可以极大地减少数据访问层的代码。既然是数据访问操作的统一标准,拿肯定是定义了各种各样和数据访问相关的接口,SpringDataRepository抽象的根接口是Repository接口:
public interface Repository<T, ID extends Serializable> {
}
//从源码中可以看出,它接受领域类(JPA为实体类)和领域类的id类型作为类型参数
它的子接口CrudRepository定义了和CRUD操作相关的内容:
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
/**
* 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 extends T> S save(S entity);
/**
* Saves all given entities.
*
* @param entities
* @return the saved entities
* @throws IllegalArgumentException in case the given entity is {@literal null}.
*/
<S extends T> Iterable<S> save(Iterable<S> 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<T> findAll();
/**
* Returns all instances of the type with the given IDs.
*
* @param ids
* @return
*/
Iterable<T> findAll(Iterable<ID> 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的子接口PagingAndSortingRepository定义了分页和排序操作相关的内容:
/**
* Extension of {@link CrudRepository} to provide additional methods to retrieve entities using the pagination and
* sorting abstraction.
*
* @author Oliver Gierke
* @see Sort
* @see Pageable
* @see Page
*/
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
/**
* Returns all entities sorted by the given options.
*
* @param sort
* @return all entities sorted by the given options
*/
Iterable<T> findAll(Sort sort);
/**
* Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
*
* @param pageable
* @return a page of entities
*/
Page<T> findAll(Pageable pageable);
}
不同的数据访问计数也提供了不同的Repository,如SpringDataJPA有JpaRepository、SpringDataMongoDB有MongoRepository。
SpringData项目还给我们提供了一个激动人心的功能,即可以根据属性名进行记数、删除、查询方法等操作,如:
public interface PersonRepository extends Repository<Person,Long>{
//按照年龄计数
Long countByAge(Integer age);
//按照名字删除
Long deleteByName(String name);
//按照名字查询
List<Person>findByName(String name);
//按照名字和地址查询
List<Person>findByNameAndAddress(String name,String address);
}
如何开始?
定义数据访问层
使用SpringDataJPA建立数据访问层十分简单,只需定义一个继承JpaRepository的接口即可,定义如下:
public interface PersonRepository extends JpaRepository<Person,Long>{
//定义数据访问操作的方法
}
配置使用SpringDataJPA
在Spring环境中,使用SprintDataJPA可通过@EnableJpaRepositories注解来开启SpringDataJPA的支持,@EnableJpaRepositories接受的value参数用来扫描参数访问层所在包下的数据访问的接口定义。
@Configuration
@EnableJpaRepositories("com.camile.repos")
public class JpaConfiguration{
@Bean
public EntityManagerFactory EntityManagerFactory(){
}
//还需配置DataSource、PlatformTransactionManager等相关必须bean
}
定义查询方法
在讲解查询方法前,假设我们有一张数据表叫PERSON,有ID(Number)、NAME(Varchar2)、AGE(Number)、ADDRESS(Varchar2)几个字段;对应的实体类叫Person,分别有id(Long)、name(String)、age(Integer)、address(String)。下面我们就以这个简单的实体查询作为演示。
根据属性名查询
SpringDataJPA支持通过定义在Repository接口中的方法名定义查询,而方法名是根据实体类的属性名来确定的。
常规查询。根据属性名来定义查询方式,示例如下:
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来代替。
而Like和And这类查询关键字:
Keyword Sample | JPQL | snippet |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = 1? |
Between | findByStartDateBetween | … where x.startDate between 1? and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (参数前面加 %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (参数后面加 %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (参数两边加 %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> age) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
限制结果数量。结果数量是用top和first关键字实现的,例如:
public interface PersonRepository extends JpaRepository<Person,Longs>{
//获得符合查询条件的前10条数据
List<Person>findFirst10ByName(String name);
//获得符合查询条件的前30条数据
List<Person>findTop30ByName(String name);
}
使用JPA的NamedQuery查询
SpringDtaJPA支持用JPA的NameQuery来定义查询方法,即一个名称映射一个查询语句。定义如下:
@entitya
@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>findByName(String name);
}
使用@Query查询
- 使用参数索引。SpringDataJPA还支持用@Query注解在接口的方法上实现查询,例如:
public interface PersonRepository extends JpaRepository<Person,Long>{
@Query("select p from Person p where p.address=?1")
List<Person>findByAddress(String address);
}
- 使用命名参数。上面的例子是使用参数的索引号来查询的,在SpringDataJPA里还支持在语句里用名称来匹配查询参数,例如:
public interface PersonRepository extends JpaRepository<Person,Long>{
@Query("select p from Person p where p.address = :address")
List<Person>findByAddress(@Param("address")String address);
}
- 使用命名参数。上面的例子是使用参数的索引号来查询的,在SpringDataJPA里还支持在语句里用名称来匹配查询参数的,例如:
public interface PersonRepository extends JpaRepository<Person,Long>{
@Query("select p from Person p where p.address = :address")
List<Person>findByAddress(@Param("address")String address);
} - 更新查询。SpringDtaJPA支持@Modifying和@Query注解组合来事件更新查询,例如:
public interface PersonRepository extends JpaRepository<Person,Long>{
@Modifying
@Transactional
@Query("update Person p set p.name=?1")
int setName(String name);//int 返回值表示更新语句影响的行数
}
Specification
JAP提供了基于准则查询的方式,即Criteria查询。而SpringDataJPA提供了一个Specification(规范)接口让我们可以更方便地构造准则查询,Specification接口定义了一个toPredicate方法用来构造查询条件。
- 定义。我们接口类必须实现JpaSpecificationExecutor接口,代码如下:
public interface PersonRepository extends JpaRepository<Person,Long>,JpaSpecificationExecutor<Person>{
}
排序与分页
SpringDataJPA充分考虑了在实际开发中所必需的排序和分页的场景,为我们提供了Sort类以及Page接口和Pageable接口。
- 比如现在有个DAO层的类继承了JpaRepository
public interface PersonRepository extends JpaRepository<Person,Long>{
List<Person>findByName(String name,Sort sort);
Page<Person>findByName(String name,Pageable pageable);
}
- 使用排序
List<Person>people = personRepository.findByName("xx",new Sort(Direction.ASC,"age"));
- 使用分页
Page<Person>people2 = personRepository.findByName("xx",new PageRequest(0,10));
自定义Repository的实现
hibernate提供了根据实体类自动维护数据表结构的功能,可通过在application.properties文件里的spring.data.hibernate.ddl-auto进行配置,值如下:
- create:启动时删除上一次生成的表,并根据实体类生成表,表中数据会被清空。
- create-drop:启动时根据实体类生成表,sessionFactory关闭时表会被删除。
- update:启动时会根据实体类生成表,当实体类属性变动的时候,表结构也会更新,在初期开发阶段使用此选项。
- validate:启动时验证实体类和数据表是否一致,在我们的数据结构稳定时采用此选项。
- none:不采取任何措施。