SpringBoot开发中的一些小细节(六)

SpringBoot开发中的一些小细节(六)

2019, Jan 08    

前言

本文主要总结于如下的官方文档的部分内容,如有翻译不当理解不当的地方,欢迎留言指正
Spring官方文档
Spring JDBC官方文档
Spring MongoDB官方文档

JPA

JAVA Persistence API,java持久化的api

JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易地掌握。JPA基于非侵入式原则设计,因此可以很容易地和其它框架或者容器集成。

MongoDB与JDBC

mongoDB和JDBC(JAVA DATABASE CONNECTIVITY)最顶级的抽象类CrudRepository有很多相似的地方,下面就来看看CrudRepository和MongoRepository的对比,根据源码来看看两者之间的关系

MongoRepository

@NoRepositoryBean
public interface MongoRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

	@Override
	<S extends T> List<S> saveAll(Iterable<S> entites);

	@Override
	List<T> findAll();

	@Override
	List<T> findAll(Sort sort);

	<S extends T> S insert(S entity);

	<S extends T> List<S> insert(Iterable<S> entities);

	@Override
	<S extends T> List<S> findAll(Example<S> example);

	@Override
	<S extends T> List<S> findAll(Example<S> example, Sort sort);
}

CrudRepository

public interface CrudRepository<T, ID extends Serializable>
  extends Repository<T, ID> {

  <S extends T> S save(S entity);      

  Optional<T> findById(ID primaryKey);

  Iterable<T> findAll();               

  long count();                        

  void delete(T entity);               

  boolean existsById(ID primaryKey);   

  // … more functionality omitted.
}

两者的接口函数真的很相似
如果继续往上追根溯源,我们会发现:

public interface MongoRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T>

MongoRepository继承了PagingAndSortingRepository这个接口,这个接口主要是用来实现查询的分页的功能,点进去再仔细一看,就会发现:

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID>

这个时候,就会发现MongoRepository的最上层,就是CrudRepository,由此可见,CrudRepository就是MongoRepository的最上层的抽象类,因此两者之间有这么多相似的地方也就没有什么奇怪了。但是因为MongoDb是一种非关系型的数据库,因此实现的方式才会有所区别。

CrudRepository派生的查询(Derive的 Query)

在CrudRepository中有派生的count以及派生的delete等其他的查询,这样可以通过自定义的驼峰的形式来进行的CRUD等操作。

Derived Count Query

interface UserRepository extends CrudRepository<User, Long> {
    long countByLastname(String lastname);
}

Derived Delete Query

interface UserRepository extends CrudRepository<User, Long> {
    long deleteByLastname(String lastname);
    List<User> removeByLastname(String lastname);
}

MongoRepository中派生的查询

既然MongoRepository是CrudRepository的派生类,那么应该也可以实现相应的派生的自定义的CRUD操作
这个Repository proxy有两种方式来派生出来查询的query

使用Name来生成Query

By deriving the query from the method name directly.

例子

public interface CustomProjectRepo extends MongoRepository<CustomProject,Integer> {
    List<CustomProject> findByIdInAndNameRegex(List<Integer> ids, String name);
    List<CustomProject> findByIdIn(List<Integer> ids);
    List<CustomProject> findByName(String name);
    Optional<CustomProject> findByPathWithNamespace(String pathWithNamespace);
    List<CustomProject> findByNamespaceIdIn(List<Integer> groupIds);
}

无需自己另外实现,函数会自行的解析,重上到下的依次实现的功能:

  • id在ids中,并且名字符合name的正则的的CustomProject
  • id在ids中的CustomProject
  • 找到对应name的CustomProject
  • 找到指定pathWithNamespace的CustomProject
  • 找到namespaceId匹配groupIds中的CustomProject

基本用法

这种机制来创造MongoDB Query,很方便,只需要相应的前缀加上相应的名字,使用java标准的命名方式(驼峰的方式),该机制就会自动的开始解析:

  • find…By
  • read…By
  • query…By
  • get…By

除此之外,这些语句还能够包含其他更加深层次的表达式,比如:

  • Distinct 去重
  • IgnoreCase 无视大小写
  • OrderBy…Asc 按照某个属性升序
  • OrderBy…Desc 按照某个属性降序
  • Between 在…之间
  • LessThan 少于
  • GreaterThan 多于
  • Like 模糊查询

第一个By是当做最基础的查询语句的基础,除此之外,还可以使用And和Or来连接表达式

歧义处理

List<Person> findByAddressZipCode(ZipCode zipCode);

可能会被解析成AddressZipCode当成一个字段,AddressZip Code,也有可能Address ZipCode.为了解决这种歧义,MongoRepository使用了下划线来手动设置

List<Person> findByAddress_ZipCode(ZipCode zipCode);

这也是为什么下划线被设置成了保留字,而推荐用户使用标准java命名方式的原因

特殊参数处理

可以传入特殊的参数,比如Pageable和sort,可以动态的设置分页和排序,例子:

Page<User> findByLastname(String lastname, Pageable pageable);

Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);

List<User> findByLastname(String lastname, Pageable pageable);

第一种返回page的方式在查询的结果集比较庞大的时候,代价也就会特别大,因此可以选择返回Slice,Slice可以知道下一个slice是否是有用的,能够在大的数量级下产生作用。

限制结果集的数量

就像在数据查询的时候,可以查第一条或者若干条和最上面的一条或者若干条,MongoDBRepository也可以实现这个功能,同时也可以实现分页和排序的功能

User findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

List<User> findFirst10ByLastname(String lastname, Sort sort);

List<User> findTop10ByLastname(String lastname, Pageable pageable);

手动定义来生成Query

This section covers repository customization and how fragments form a composite repository.

当需要的方法不能直接通过派生的机制直接派生的时候,就需要自己手动定义了,步骤如下

定义自定义接口

interface CustomizedUserRepository {
  void someCustomMethod(User user);
}

实现自定义接口中的函数

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
  public void someCustomMethod(User user) {
  // Your custom implementation
  }
}

原始库的修改

集成自定义的接口即可

interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}

总结

还有MongoTemplate的方式来实现各种Query,但是由于工作中遇到过了一次bug,mongo中的其中一个库的数据突然不见了,由于mongo没有什么特定的sql语句可以查询,而且使用的是mongoTemplate,出现的地方很多,排查bug的时候耗费了很大的精力。从那之后,前辈就推荐使用repo的方式来进行对mongoDB的Crud操作了。