29 June 2019

在SpringBoot项目中我们经常会提取一些公共的方法封装起来,放到父类中;子类继承父类后复用这些方法,如果自己有特殊需要再写自己特有的方法。 一般的web项目核心功能离不开对数据库数据的增删改查操作,因此封装公共的service和repository是很有必要的。

下面针对我们一个项目的封装过程做个总结

## 封装Repository

  • 基础Repository 除了继承Spring data jpa 中常用的Repository接口外,额外添加自己常用的方法:
     @NoRepositoryBean
     public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>
          , PagingAndSortingRepository<T, ID>
          , JpaSpecificationExecutor<T> {
      List<Object[]> queryBySql(String sql);
      List<T> query(String sql);
      Object getBySql(String sql);
      T get(String sql);
      int execute(String sql);
      Class<T> getDataClass();
     }
    
  • 由于该接口不纳入repository管理,所以应增加@NoRepositoryBean 注解来标识
  • 针对上述接口增加其实现类:BaseRepositoryImpl
     public class BaseRepositoryImpl<T, ID extends Serializable>
          extends SimpleJpaRepository<T, ID>
          implements BaseRepository<T, ID> {
      private final EntityManager entityManager;
      private Class<T> klass;
    
    
      BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
                         EntityManager entityManager) {
          super(entityInformation, entityManager);
          this.entityManager = entityManager;
          this.klass = (Class<T>) entityInformation.getJavaType();
      }
    
      @Override
      public List<Object[]> queryBySql(String sql) {
          return entityManager.createNativeQuery(sql).getResultList();
      }
    
      @Override
      public Object getBySql(String sql) {
          List list = entityManager.createNativeQuery(sql).getResultList();
          if(list.isEmpty()){
              return null;
          }
          return list.get(0);
      }
    
      @Override
      public T get(String sql) {
          List<T> list =  entityManager.createNativeQuery(sql,klass).getResultList();
          return list.get(0);
      }
    
      @Override
      public int execute(String sql) {
          return entityManager.createNativeQuery(sql).executeUpdate();
      }
    
      @Override
      public Class<T> getDataClass() {
          return klass;
      }
    
      @Override
      public List<T> query(String sql) {
          return entityManager.createNativeQuery(sql,klass).getResultList();
      }
     }
    
  • 建自定义RepositoryFactoryBean
    • 接下来我们来创建一个自定义的RepositoryFactoryBean来代替默认的RepositoryFactoryBean。
    • RepositoryFactoryBean负责返回一个RepositoryFactory,Spring Data Jpa 将使用RepositoryFactory来创建Repository具体实现,
    • 这里我们用BaseRepositoryImpl代替SimpleJpaRepository作为Repository接口的实现。这样我们就能够达到为所有Repository添加自定义方法的目的。
        public class BaseRepositoryFactoryBean<JR extends JpaRepository<T, ID>, T, ID extends Serializable>
            extends JpaRepositoryFactoryBean<JR, T, ID> {
        public BaseRepositoryFactoryBean(Class<? extends JR> repositoryInterface) {
            super(repositoryInterface);
        }
      
        @Override
        protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
            return new BaseRepositoryFactory(entityManager);
        }
      
        private static class BaseRepositoryFactory<T, ID extends Serializable> extends JpaRepositoryFactory {
            private final EntityManager entityManager;
            public BaseRepositoryFactory(EntityManager entityManager) {
                super(entityManager);
                this.entityManager = entityManager;
            }
      
            @Override
            protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
                JpaEntityInformation<?, Serializable> entityInformation = this.getEntityInformation(information.getDomainType());
                Object repository = this.getTargetRepositoryViaReflection(information, new Object[]{entityInformation, entityManager});
                Assert.isInstanceOf(BaseRepositoryImpl.class, repository);
                return (JpaRepositoryImplementation)repository;
            }
      
            @Override
            protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
                return BaseRepositoryImpl.class;
            }
        }
        }
      
  • 配置Jpa factory class
    • 由于我们实现了自定义的Repository Factory类,所以需要配置Jpa使用我们自定义的BaseRepositoryFactoryBean。
    • Spring支持使用标注进行配置,应在启动主类中添加标注@EnableJpaRepositories(repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class),,例:
        @SpringBootApplication   
        @EnableJpaRepositories(basePackages = "cn.enilu.flash.dao", repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class)    
        public class ApiApplication extends SpringBootServletInitializer {
      
        public static void main(String[] args) {
            SpringApplication.run(ApiApplication.class);
        }
        }
      
  • 至此公共的Repository封装完毕,然后再自己的repository中继承BaseRepository即可使用封装的公共方法 ```java public interface UserRepository extends BaseRepository<User,Long> { }


 ## 封装Service

 针对service,我们根据自己业务情况封装长用的增删改查方法即可,由于service涉及业务较多封装起来也比较复杂。总体步骤比较简单,只是具体的逻辑稍微复杂些,尤其涉及到复杂的查询,分页查询
 - 定义service接口,针对CRUD分别定义4中领域的接口
 - 实现service接口


 ### service接口定义

 这里我们针对CRUD分别定义四个接口:InsertService,DeleteService,UpdateService,SelectService,并且再定义一个CrudService来继承上面四个接口,具体代码如下:

 - InsertService

 ```java
 public interface InsertService<T, ID> {

     /**
      * 添加一条数据
      *
      * @param record 要添加的数据
      * @return 添加后生成的主键
      */
     T insert(T record);
 }

  • DeleteService
     public interface DeleteService<ID> {
    
      /**
       * 根据主键删除记录
       *
       * @param id 主键
       */
      void delete(ID id);
    
      /**
       * 根据主键删除记录
       *
       * @param ids 主键集合
       */
      void delete(Iterable<ID> ids);
    
      /**
       * 清空表数据
       */
      void clear();
     }
    
  • UpdateService
 public interface UpdateService <T, ID> {
     /**
      * 修改记录信息
      *
      * @param record 要修改的对象
      * @return 返回修改的记录
      */
     T update(T record);
     /**
      * 添加或修改记录
      * @param record 要添加或修改的对象
      * @return 返回添加或修改的记录
      */
     T saveOrUpdate(T record);
 }
  • SelectService
 public interface SelectService <T, ID> {

     /**
      * 根据主键查询
      * @param id 主键
      * @return 查询结果,无结果时返回{@code null}
      */
     T get(ID id);

     /**
      * 根据多个主键查询
      * @param ids 主键集合
      * @return 查询结果,如果无结果返回空集合
      */
     List<T> query(Iterable<ID> ids);

     /**
      * 查询所有结果
      * @return 所有结果,如果无结果则返回空集合
      */
     List<T> queryAll();

     /**
      * 查询所有结果
      * @return 获取分页结果
      */
     Page<T> queryPage(Page<T> page);

     /**
      * 根据多个条件查询列表数据
      * @param filters
      * @return
      */
     List<T> queryAll(List<SearchFilter> filters);

     /**
      * 根据多个条件查询列表数据,并排序
      * @param filters
      * @param sort
      * @return
      */
     List<T> queryAll(List<SearchFilter> filters, Sort sort);

     /**
      * 根据的单个条件查询列表数据
      * @param filter
      * @return
      */
     List<T> queryAll(SearchFilter filter);

     /**
      * 根据的单个条件查询列表数据
      * @param filter
      * @param sort
      * @return
      */
     List<T> queryAll(SearchFilter filter,Sort sort);
 }

-CurdService

 public interface CrudService <T, ID> extends
         InsertService<T, ID>,
         UpdateService<T,ID>,
         DeleteService<ID>,
         SelectService<T, ID> {
 }

### 实现Service接口 我们通过BaseService实现上述接口


 public abstract  class BaseService<T, ID extends Serializable, R extends BaseRepository<T, ID>>
         implements CrudService<T, ID> {
     @Autowired
     private R dao;


     @Override
     public void delete(ID id) {
         dao.deleteById(id);
     }

     @Override
     public void delete(Iterable<ID> ids) {
         Iterator<ID> iterator = ids.iterator();
         while (iterator.hasNext()) {
             ID id = iterator.next();
             dao.deleteById(id);
         }
     }

     @Override
     public T insert(T record) {
         return dao.save(record);
     }

     @Override
     public T get(ID id) {
         return  dao.getOne(id);
     }

     @Override
     public List<T> query(Iterable<ID> ids) {
         return dao.findAllById(ids);
     }

     @Override
     public List<T> queryAll() {
         return dao.findAll();
     }

     @Override
     public Page<T> queryPage(Page<T> page) {
         Pageable pageable = null;
         if(page.isOpenSort()) {
             pageable = new PageRequest(page.getCurrent()-1, page.getSize(), page.isAsc() ? Sort.Direction.ASC : Sort.Direction.DESC, page.getOrderByField());
         }else{
             pageable = new PageRequest(page.getCurrent()-1,page.getSize(), Sort.Direction.DESC,"id");
         }
         Specification<T> specification = DynamicSpecifications.bySearchFilter(page.getFilters(),dao.getDataClass());
         org.springframework.data.domain.Page<T> pageResult  = dao.findAll(specification,pageable);
         page.setTotal(Integer.valueOf(pageResult.getTotalElements()+""));
         page.setRecords(pageResult.getContent());
         return page;
     }

     @Override
     public List<T> queryAll(List<SearchFilter> filters) {
         return queryAll(filters,null);
     }

     @Override
     public List<T> queryAll(SearchFilter filter) {
         return queryAll(filter,null);
     }

     @Override
     public List<T> queryAll(List<SearchFilter> filters, Sort sort) {
         Specification<T> specification = DynamicSpecifications.bySearchFilter(filters,dao.getDataClass());
         if(sort==null){
             return dao.findAll(specification);
         }
         return dao.findAll(specification,sort);
     }

     @Override
     public List<T> queryAll(SearchFilter filter, Sort sort) {
         return queryAll(Lists.newArrayList(filter),sort);
     }

     @Override
     public T update(T record) {
         return dao.save(record);
     }

     @Override
     public T saveOrUpdate(T record) {
         return dao.save(record);
     }

     @Override
     public void clear() {
         dao.deleteAllInBatch();
     }
 }
  • 上述接口中针对复杂查询和分页查询做了通用的封装
    • 封装SearchFilter来构建复杂查询条件:
      public class SearchFilter {
          public enum Operator {
              EQ, LIKE, GT, LT, GTE, LTE,IN,ISNULL,ISNOTNULL
          }
    
          public String fieldName;
          public Object value;
          public Operator operator;
          public  static SearchFilter build(String fieldName, Operator operator, Object value){
              return  new SearchFilter(fieldName,operator,value);
          }
          public  static SearchFilter build(String fieldName, Operator operator){
              return  new SearchFilter(fieldName,operator);
          }
          public SearchFilter(String fieldName, Operator operator) {
              this.fieldName = fieldName;
              this.operator = operator;
          }
          public SearchFilter(String fieldName, Operator operator, Object value) {
              this.fieldName = fieldName;
              this.value = value;
              this.operator = operator;
          }
    
          /**
           * searchParams中key的格式为OPERATOR_FIELDNAME
           */
          public static Map<String, SearchFilter> parse(Map<String, Object> searchParams) {
              Map<String, SearchFilter> filters = Maps.newHashMap();
    
              for (Map.Entry<String, Object> entry : searchParams.entrySet()) {
                  // 过滤掉空值
                  String key = entry.getKey();
                  Object value = entry.getValue();
      			/*if (StringUtils.isBlank((String) value)) {
      				continue;
      			}*/
    
                  // 拆分operator与filedAttribute
                  String[] names = StringUtils.split(key, "_");
                  if (names.length != 2) {
                      throw new IllegalArgumentException(key + " is not a valid search filter name");
                  }
                  String filedName = names[1];
                  Operator operator = Operator.valueOf(names[0]);
    
                  // 创建searchFilter
                  SearchFilter filter = new SearchFilter(filedName, operator, value);
                  filters.put(key, filter);
              }
    
              return filters;
          }
      }
    
    • 通过DynamicSpecifications来解析SearchFilter查询条件,构建Spring data jpa复杂查询对象Predicate
      public class DynamicSpecifications {
          public static <T> Specification<T> bySearchFilter(final Collection<SearchFilter> filters, final Class<T> entityClazz) {
              return new Specification<T>() {
                  @Override
                  public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                      if (filters!=null && !filters.isEmpty()) {
    
                          List<Predicate> predicates = Lists.newArrayList();
                          for (SearchFilter filter : filters) {
                              // nested path translate, 如Task的名为"user.name"的filedName, 转换为Task.user.name属性
                              String[] names = StringUtils.split(filter.fieldName, ".");
                              Path expression = root.get(names[0]);
                              for (int i = 1; i < names.length; i++) {
                                  expression = expression.get(names[i]);
                              }
                              // logic operator
                              switch (filter.operator) {
                                  case EQ:
                                      predicates.add(builder.equal(expression, filter.value));
                                      break;
                                  case LIKE:
                                      predicates.add(builder.like(expression, "%" + filter.value + "%"));
                                      break;
                                  case GT:
                                      predicates.add(builder.greaterThan(expression, (Comparable) filter.value));
                                      break;
                                  case LT:
                                      predicates.add(builder.lessThan(expression, (Comparable) filter.value));
                                      break;
                                  case GTE:
                                      predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) filter.value));
                                      break;
                                  case LTE:
                                      predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) filter.value));
                                      break;
                                  case IN:
                                      predicates.add(expression.in(filter.value));
                                      break;
                                  case ISNULL:
                                      predicates.add(expression.isNull());
                                      break;
                                  case ISNOTNULL:
                                      predicates.add(expression.isNotNull());
                                      break;
                              }
                          }
    
                          // 将所有条件用 and 联合起来
                          if (!predicates.isEmpty()) {
                              return builder.and(predicates.toArray(new Predicate[predicates.size()]));
                          }
                      }
    
                      return builder.conjunction();
                  }
              };
          }
      }
    
    • 在BaseService实现类中,我们使用了自己封装的一个分页对象Page来做分页查询:
      public class Page<T>  {
          /**
           * 该操作只是为了忽略RowBounds属性
           *
           *
           */
          private transient int offset;
          /**
           * 该操作只是为了忽略RowBounds属性
           *
           *
           */
          private transient int limit;
    
          /**
           * 总数
           */
          private int total;
    
          /**
           * 每页显示条数,默认 10
           */
          private int size = 10;
    
          /**
           * 总页数
           */
          private int pages;
    
          /**
           * 当前页
           */
          private int current = 1;
    
          /**
           * 查询总记录数(默认 true)
           */
          private transient boolean searchCount = true;
    
          /**
           * 开启排序(默认 true) 只在代码逻辑判断 并不截取sql分析
           *
           *
           **/
          private transient boolean openSort = true;
    
    
          /**
           * <p>
           * SQL 排序 ASC 集合
           * </p>
           */
          private transient List<String> ascs;
          /**
           * <p>
           * SQL 排序 DESC 集合
           * </p>
           */
          private transient List<String> descs;
    
          /**
           * 是否为升序 ASC( 默认: true )
           *
           * @see #ascs
           * @see #descs
           */
          private transient boolean isAsc = true;
    
          /**
           * <p>
           * SQL 排序 ORDER BY 字段,例如: id DESC(根据id倒序查询)
           * </p>
           * <p>
           * DESC 表示按倒序排序(即:从大到小排序)<br>
           * ASC 表示按正序排序(即:从小到大排序)
           *
           * @see #ascs
           * @see #descs
           * </p>
           */
          private transient String orderByField;
    
          public Page() {
    
          }
    
          public Page(int current, int size, String orderByField) {
    
              this.setOrderByField(orderByField);
          }
    
          public Page(int current, int size, String orderByField, boolean isAsc) {
              this(current, size, orderByField);
              this.setAsc(isAsc);
          }
    
          /**
           * <p>
           * 分页构造函数
           * </p>
           *
           * @param current 当前页
           * @param size    每页显示条数
           */
          public Page(int current, int size) {
              this(current,size,true);
          }
    
          public Page(int current, int size, boolean searchCount) {
              this(current, size, searchCount, true);
          }
    
          public Page(int current, int size, boolean searchCount, boolean openSort) {
    
              setOffset(offsetCurrent(current, size));
              setLimit(size);
              if (current > 1) {
                  this.current = current;
              }
              this.size = size;
              this.searchCount = searchCount;
              this.openSort = openSort;
          }
    
          protected static int offsetCurrent(int current, int size) {
              if (current > 0) {
                  return (current - 1) * size;
              }
              return 0;
          }
    
          public int offsetCurrent() {
              return offsetCurrent(this.current, this.size);
          }
    
          public boolean hasPrevious() {
              return this.current > 1;
          }
    
          public boolean hasNext() {
              return this.current < this.pages;
          }
    
          public int getTotal() {
              return total;
          }
    
          public Page setTotal(int total) {
              this.total = total;
              return this;
          }
    
          public int getSize() {
              return size;
          }
    
          public Page setSize(int size) {
              this.size = size;
              return this;
          }
    
          public int getPages() {
              if (this.size == 0) {
                  return 0;
              }
              this.pages = this.total / this.size;
              if (this.total % this.size != 0) {
                  this.pages++;
              }
              return this.pages;
          }
    
          public int getCurrent() {
              return current;
          }
    
          public Page setCurrent(int current) {
              this.current = current;
              return this;
          }
    
          public boolean isSearchCount() {
              return searchCount;
          }
    
          public Page setSearchCount(boolean searchCount) {
              this.searchCount = searchCount;
              return this;
          }
    
          /**
           * @see #ascs
           * @see #descs
           */
          @Deprecated
          public String getOrderByField() {
              return orderByField;
          }
    
          /**
           * @see #ascs
           * @see #descs
           */
          public Page setOrderByField(String orderByField) {
              if (StringUtils.isNotEmpty(orderByField)) {
                  this.orderByField = orderByField;
              }
              return this;
          }
    
          public boolean isOpenSort() {
              return openSort;
          }
    
          public Page setOpenSort(boolean openSort) {
              this.openSort = openSort;
              return this;
          }
    
          public List<String> getAscs() {
              return orders(isAsc, ascs);
          }
    
          private List<String> orders(boolean condition, List<String> columns) {
              if (condition && StringUtils.isNotEmpty(orderByField)) {
                  if (columns == null) {
                      columns = new ArrayList<>();
                  }
                  if (!columns.contains(orderByField)) {
                      columns.add(orderByField);
                  }
              }
              return columns;
          }
    
          public Page setAscs(List<String> ascs) {
              this.ascs = ascs;
              return this;
          }
    
          public List<String> getDescs() {
              return orders(!isAsc, descs);
          }
    
          public Page setDescs(List<String> descs) {
              this.descs = descs;
              return this;
          }
    
          /**
           * @see #ascs
           * @see #descs
           */
          @Deprecated
          public boolean isAsc() {
              return isAsc;
          }
    
          /**
           * @see #ascs
           * @see #descs
           */
          public Page setAsc(boolean isAsc) {
              this.isAsc = isAsc;
              return this;
          }
    
          public int getOffset() {
              return offset;
          }
    
          public Page setOffset(int offset) {
              this.offset = offset;
              return this;
          }
    
          public int getLimit() {
              return limit;
          }
    
          public Page setLimit(int limit) {
              this.limit = limit;
              return this;
          }
          /**
           * 查询数据列表
           */
          private List<T> records = Collections.emptyList();
    
          /**
           * 查询参数
           */
          private transient Map<String, Object> condition;
          private transient  List<SearchFilter> filters;
    
    
          public List<T> getRecords() {
              return records;
          }
    
          public Page<T> setRecords(List<T> records) {
              this.records = records;
              return this;
          }
    
          public Map<String, Object> getCondition() {
              return condition;
          }
    
          public Page<T> setCondition(Map<String, Object> condition) {
              this.condition = condition;
              return this;
          }
    
          public List<SearchFilter> getFilters() {
              return filters;
          }
    
          public void setFilters(List<SearchFilter> filters) {
              this.filters = filters;
          }
          public void addFilter(SearchFilter filter){
              if(filter==null){
                  return ;
              }
              if(filters==null){
                  filters = Lists.newArrayList();
              }
              filters.add(filter);
          }
          public void addFilter(String fieldName, SearchFilter.Operator operator, Object value){
              if(!StringUtils.isNullOrEmpty(value)){
                  addFilter(SearchFilter.build(fieldName,operator,value));
              }
          }
          public void addFilter(String fieldName, SearchFilter.Operator operator){
              addFilter(SearchFilter.build(fieldName,operator));
          }
    
          @Override
          public String toString() {
              StringBuilder pg = new StringBuilder();
              pg.append(" Page:{ [").append(super.toString()).append("], ");
              if (records != null) {
                  pg.append("records-size:").append(records.size());
              } else {
                  pg.append("records is null");
              }
              return pg.append(" }").toString();
          }
    
      }
    
    
  • 基本的封装已经完毕,下面我们在自己的service中继承BaseService来使用封装的方法,比如有个用户服务类UserService
 @Service
 public class UserService  extends BaseService<User,Long,UserRepository> {

 }
  • 在controller中使用上面的UserService
 @RestController
 @RequestMapping("/user")
 public class UserController extends BaseController {
     @Autowired
     private UserService userService;   
     /**
      * 分页查询
      */
     @RequestMapping(value = "/list",method = RequestMethod.GET)
     public Object list(@RequestParam(required = false) String userName, @RequestParam(required = false) String mobile) {
         Page<Cfg> page = new PageFactory<Cfg>().defaultPage();
         page.addFilter(SearchFilter.build("userName", SearchFilter.Operator.LIKE, cfgName));
         page.addFilter(SearchFilter.build("mobile", SearchFilter.Operator.EQ, mobile));
         page = userService.queryPage(page);
         return Rets.success(page);
     }
 }    

至此一个基础的service和repository已经封装完毕并且可以运行了,当然上面的封装还不完善,你可以根据自己的实际项目中需求做更多的封装:比如支持Or查询,比如controller层自动接收查询参数等等





blog comments powered by Disqus
Fork me on GitHub