查询方法
CrudRepository中只定义了通过主键查询的方式,这显然不能满足需求。Spring提供了查询方法来解决这样问题。查询方法包括:
- find...By
- get...By
- read...By
将返回单个结果T,或者多个结果Iterable<T>, List<T>, Collection<T>,Page<T>。下面是例子:
public interface BookRepository extends PagingAndSortingRepository<Book, Long>{ // 在PagingAndSortingRepository外增加三个查询方法,对应UNIQUE KEY和KEY的查询。无需编写任何实现代码,Spring data会自动实现的。 Book findByIsbn(String isbn); List<Book> findByAuthor(String author); List<Book> findByPublisher(String publisher); // find...By,By后面的是条件,而find和By之前一般无特定意义,除非是SQL语句中的特定含义 // 中间的Book无意义,只是给方法名字更好的语义,等同于findByIsbn() Book findBookByIsbn(String isbn); // 这里面的distinct作为保留字有意义,Books无意义,相当于SELECT DISTICT * FROM Book Where Publisher=?, // 假定entity映射的表格为Book,属性publisher对应的列名了Publisher List<Book> findDistinctBooksByPublisher(String publisher); }作者和出版商都可能有很多的书,需要进行分页处理,我们改为:
public interface BookRepository extends PagingAndSortingRepository<Book, Long>{ Book findByIsbn(String isbn); Page<Book> findByAuthor(String author, Pageable instructions); Page<Book> findByPublisher(String publisher, Pageable instructions); }上面例子中Entity的属性都是简单类型,也可以是复杂类型,例如PersonEntity中含有Address address,而Address又具有若干属性。我们同样可以通过前面介绍的方式匹配具体的Address,但是如果我们只需要匹配Address里面的某个属性,例如邮编。可以写为:
List<Person> findByAddressPostalCode(PostalCode code); //匹配address属性里面的postalCode属性,这种写法容易引起歧义,最好采用下划线方式 Page<Person> findByAddress_PostalCode(PostalCode code, Pageable instructions);By后面放置查询条件,我们具体了解如何设置:
// ➤ By后面缺省是等于,也就是Is或者Equal,下面等同与Book findByIsbn(String isbn); Book findByIsbnIs(String isbn); Book findByIsbnEqual(String isbn); // ➤ NOT,IsNOT表示不等于 List<Book> findByIsbnIsNot(Strign isbn); List<Book> findByIsbnIsNotEqual(Strign isbn); // ➤ OR 和 AND的条件组合 List<Book> findByAuthorAndPublisher(String author, String publisher); List<Book> findByAuthorOrPublisher(String author, String publisher); // ➤ 大小写不敏感:数据库中有些列的匹配已经是设定为忽略大小写,我们也可以在方法中明确声明。但从查询效率,我们应答在表格设计中体现,例如某个KEY是大小写不敏感的。 //只是publisher忽略大小写 List<Book> findByAuthorAndPublisherIgnoreCase(String author, String publisher); //author和publisher忽略大小写 List<Book> findByAuthorIgnoreCaseAndPublisherIgnoreCase(String author, String publisher); //所有均忽略大小写 List<Book> findByAuthorAndPublisherAllIgnoreCase(String author, String publisher); // ➤ After, IsAfter,Befor,IsBefor用于对时间或日期的操作 List<Book> findByDateFoundedIsAfter(Date date); // ➤ 对于%的处理:Contains,Containing, IsContaining,StartsWith,StartingWith, IsStartingWith,EndsWith,EndingWith, IsEndingWith // 下面相当于WHERE title = '%value%' List<Book> findByTitleContains(String value); // ➤ Like: 下面相当于WHERE title = 'value',注意这和Contain等很相似,但是通配符%要放入到value之中 List<Book> findByTitleLike(String value); // ➤ Between,IsBetween List<Book> findByDateFoundedBetween(Date start, Date end); // ➤ Exists:相当于SQL中的EXISTS // ➤ True,IsTrue,False,IsFalse:对boolean的属性的检查 List<Book> findByApprovedIsFalse(); // ➤ GreaterThan,IsGreaterThan,GreaterThanEqual,IsGreaterThanEqual,LessThan,IsLessThan,LessThanEqual,IsLessThanEqual // ➤ In:表示属性的值必须等于里面的某个值,必须采用Iterable来表述 List<Book> findByAuthorIn(Iterable<String> authors); // ➤ Null,IsNull:值为null,方法不应带有参数,应为已经对值表述清楚 // ➤ Near, IsNear, Within, IsWithin常用于NoSQL,不在JPA中使用 // ➤ Regex,MatchesRegex,Matches用于正则表达式 List<Book> findByTitleRegex(String regex);
自定义的方法
Spring data很强大,但不一定能完全覆盖我们的需要,在某些情况,我们需要自定义方法。
为某个仓库提供自定义方法
1、我们定义某个仓库接口,在里面提供定义自定义的方法。
public interface TestRepositoryCustom { List<TestEntity> test(int page, int perPageNum, Predicate ... restrictions); }
2、Spring Data的仓库接口将extends含有自定义的方法的接口
public interface TestRepository extends CrudRepository<TestEntity, Long>,TestRepositoryCustom{ TestEntity findByUserName(String userName); }
3、实现自定义的方法。注意,如果这个类无需加上@Repository,但是类名必须为Spring data仓库接口加上Impl,Spring data扫描到TestRepository后,将会在同一package下面查找TestRepositoryImpl,如有,将作为Spring bean具体实例化(也就是为何我们无需加上@Repository),我们应在此具体实现自定义的方法。至于自定义的接口的名字无任何要求。
public class TestRepositoryImpl implements TestRepositoryCustom{ @PersistenceContext private EntityManager entityManager; @Override public List<TestPage> test(int page, int numPerPage, Predicate ... restrictions) { // 具体的代码 ...... } }
为所有仓库提供自定义方法
这应属于很罕见的情况,我们需要为所有的仓库都提供同样的自定义方法。
1、自定义的接口。这里extends了JpaRepository(属于Spring data,其extends了CrudRepository和PagingAndSortingRepository),以便Spring data可以扫描到。标记@NoRepositoryBean是告知Spring data不要为这个接口生成一个实现,因为只是我们通用的自定义接口,不对应具体的仓库。
@NoRepositoryBean public interface CustomRepository<T, ID extends Serializable> extends JpaRepository<T, ID>{ public void customOperation(T entity); }
2、提供实现,我们必须继承Spring data JAP提供的基础仓库类SimpleJpaRepository。SimpleJpaRepository提供了预定义的接口方法,如findOne(ID),save(T)。如果需要提供Querydsl,则应extends替换为QueryDslJpaRepository。
public class CustomRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements CustomRepository<T, ID>{ private Class<T> domainClass; private EntityManager entityManager; public CustomRepositoryImpl(Class<T> domainClass, EntityManager entityManager){ super(domainClass, entityManager); this.domainClass = domainClass; this.entityManager = entityManager; } public CustomRepositoryImpl(JpaEntityInformation<T, ?> information,EntityManager entityManager){ super(information, entityManager); this.domainClass = information.getJavaType(); this.entityManager = entityManager; } public void customOperation(T){ // code to implement custom operation } }
3、让所有仓库支持这些自定义的方法。由于我们标记了@NoRepositoryBean,Spring Data JPA不会自动扫描CustomRepositoryImpl,需要创建一个factory bean替代缺省的factory bean来完成这项工作,让扫描仓库接口后,不将仓库接口+Impl作为其实现,而指定为CustomRepositoryImpl。
public class CustomRepositoryFactoryBean<R extends JpaRepository<T, ID>, T,ID extends Serializable> extends JpaRepositoryFactoryBean<R, T, ID>{ @Override protected RepositoryFactorySupport createRepositoryFactory(EntityManager e){ return new CustomRepositoryFactory<T, ID>(e); } private static class CustomRepositoryFactory<T, ID extends Serializable> extends JpaRepositoryFactory{ private EntityManager entityManager; public CustomRepositoryFactory(EntityManager entityManager){ super(entityManager); this.entityManager = entityManager; } @Override @SuppressWarnings("unchecked") protected Object getTargetRepository(RepositoryMetadata metadata){ return new CustomRepositoryImpl<T, ID>((Class<T>) metadata.getDomainType(), this.entityManager); } @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata){ return CustomRepositoryImpl.class; } } }4、将缺省的JpaRepositoryFactoryBean修改为CustomRepositoryFactoryBean
@EnableJpaRepositories(basePackages = "cn.wei.flowingflying.chapter22.site.repositories", //Returns the FactoryBean class to be used for each repository instance. Defaults to JpaRepositoryFactoryBean. repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)