javascript
Spring Data JPA 从入门到精通~实际工作的应用场景
在實際工作中,有哪些場景會用到自定義 Repository 呢,這里列出幾種實際在工作中的應(yīng)用案例。
1. 邏輯刪除場景
可以用到上面說的兩種實現(xiàn)方式,如果有框架級別的全局自定義 Respository 那就在全局實現(xiàn)里面覆蓋默認 remove 方法,這樣就會統(tǒng)一全部只能使用邏輯刪除。但是一般是自定義一個特殊的刪除Respository,讓大家去根據(jù)不同的domain業(yè)務(wù)邏輯去選擇使用此接口即可。
2. 當有業(yè)務(wù)場景要覆蓋 SimpleJpaRepository 默認實現(xiàn)的時候
這種一般是具體情況具體分析的,一般實現(xiàn)特殊化的自定義 Respository 即可。
3. UUID 與 ID 的情況
經(jīng)常在實際生產(chǎn)中會有這樣的場景,對外暴露的是 UUID 查詢方法,而對內(nèi)呢暴露的是 Long 類型的 ID,這時候我們就可以自定義一個 FindByIDOrUUID 的底層實現(xiàn)方法,在自定義的 Respository 接口里面。
4. 使用 Querydsl
Spring Data JPA 里面還幫我們做了 QuerydslJpaRepository 用來支持 Querydsl 的查詢方法,當我們引入 Querydsl 的時候 Spring 就會自動幫我們把 SimpleJpaRepository 的實現(xiàn)切換到 QuerydslJpaRepository 的實現(xiàn)。
5. 動態(tài)查詢條件
由于 Data JPA 里面的 query method 或者 @query 注解不支持動態(tài)查詢條件,正常情況下將動態(tài)條件寫在 manager 或者 service 里面。這個時候如果是針對資源的操作,并且和業(yè)務(wù)無關(guān)的查詢的時候可以放在自定義 Repository 里面(有個缺點就是不能使用 SimpleJpaRepository,里面的很多優(yōu)秀的默認是實現(xiàn)方法,在實際工作中還是放在 service 和 manager 中多一些,只是給大家舉個例子,知道有這么回事就行)。實例如下:
//我們假設(shè)要根據(jù)條件動態(tài)查詢訂單 public interface OrderRepositoryCustom {Page<Order> findAllByCriteria(OrderCriteria criteria); // 定義一個訂單的定制化Repository查詢方法,當然實際生產(chǎn)過程中,這里面可能不止一個方法。 }public class OrderRepositoryImpl implements OrderRepositoryCustom { @PersistenceContextEntityManager entityManager; /*** 一個動態(tài)條件的查詢方法*/public List<Order> findAllByCriteria(OrderCriteria criteria) {// 查詢條件列表final List<String> andConditions = new ArrayList<String>();final Map<String, Object> bindParameters = new HashMap<String, Object>();// 動態(tài)綁定參數(shù)和要查詢的條件if (criteria.getId() != null) {andConditions.add("o.id = :id");bindParameters.put("id", criteria.getId());}if (!CollectionUtils.isEmpty(criteria.getStatusCodes())) {andConditions.add("o.status.code IN :statusCodes");bindParameters.put("statusCodes", criteria.getStatusCodes());}if (andConditions.isEmpty()) {return Collections.emptyList();}// 動態(tài)創(chuàng)建queryfinal StringBuilder queryString = new StringBuilder();queryString.append("SELECT o FROM Order o");// 動態(tài)拼裝條件Iterator<String> andConditionsIt = andConditions.iterator();if (andConditionsIt.hasNext()) {queryString.append(" WHERE ").append(andConditionsIt.next());}while (andConditionsIt.hasNext()) {queryString.append(" AND ").append(andConditionsIt.next());}// 添加排序queryString.append(" ORDER BY o.id");// 創(chuàng)建 typed query.final TypedQuery<Order> findQuery = entityManager.createQuery(queryString.toString(), Order.class);// 綁定參數(shù)for (Map.Entry<String, Object> bindParameter : bindParameters.entrySet()) {findQuery.setParameter(bindParameter.getKey(), bindParameter.getValue());}//返回查詢,結(jié)果。return findQuery.getResultList();} } //實際中此種就比較少用了,大家知道有這么回事,真是遇到特殊場景必須要用了,可以用此方法實現(xiàn)。6. 擴展 JpaSpecificationExecutor 使其更加優(yōu)雅
當我們動態(tài)查詢的時候經(jīng)常會出現(xiàn)下面的代碼邏輯,寫起來老是感覺有點不是特別優(yōu)雅,且有點重復(fù)的感覺:
PageRequest pr = new PageRequest(page - 1, rows, Direction.DESC, "id");Page pageData = memberDao.findAll(new Specification() {@Overridepublic Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {List<Predicate> predicates = new ArrayList<>();if (isNotEmpty(userName)) {predicates.add(cb.like(root.get("userName"), "%" + userName + "%"));}if (isNotEmpty(realName)) {predicates.add(cb.like(root.get("realName"), "%" + realName + "%"));}if (isNotEmpty(telephone)) {predicates.add(cb.equal(root.get("userName"), telephone));}query.where(predicates.toArray(new Predicate[0]));return null;}}, pr);使用了自定義的復(fù)雜查詢,我們可以做到如下效果:
Page pageData = userDao.findAll(new MySpecification<User>().and(Cnd.like("userName", userName),Cnd.like("realName", realName),Cnd.eq("telephone", telephone) ).asc("id"), pr);如果對 Spring MVC 比較熟悉的話,可以更進一步把其查詢提交和規(guī)則直接封裝到 HandlerMethodArgumentResolver 里面,把參數(shù)自動和規(guī)則匹配起來。
我們可以對如下代碼進行參考,感覺實現(xiàn)的還不錯,此段代碼可以作參考,只是實現(xiàn)的還有點不完整,如下:
/*** 擴展Specification* @param <T>*/ public class MySpecification<T> implements Specification<T> {/*** 屬性分隔符*/private static final String PROPERTY_SEPARATOR = ".";/*** and條件組*/List<Cnd> andConditions = new ArrayList<>();/*** or條件組*/List<Cnd> orConditions = new ArrayList<>();/*** 排序條件組*/List<Order> orders = new ArrayList<>();@Overridepublic Predicate toPredicate(Root<T> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {Predicate restrictions = cb.and(getAndPredicates(root, cb));restrictions = cb.and(restrictions, getOrPredicates(root, cb));cq.orderBy(getOrders(root, cb));return restrictions;}public MySpecification and(Cnd... conditions) {for (Cnd condition : conditions) {andConditions.add(condition);}return this;}public MySpecification or(Collection<Cnd> conditions) {orConditions.addAll(conditions);return this;}public MySpecification desc(String property) {this.orders.add(Order.desc(property));return this;}public MySpecification asc(String property) {this.orders.add(Order.asc(property));return this;}private Predicate getAndPredicates(Root<T> root, CriteriaBuilder cb) {Predicate restrictions = cb.conjunction();for (Cnd condition : andConditions) {if (condition == null) {continue;}Path<?> path = this.getPath(root, condition.property);if (path == null) {continue;}switch (condition.operator) {case eq:if (condition.value != null) {if (String.class.isAssignableFrom(path.getJavaType()) && condition.value instanceof String) {if (!((String) condition.value).isEmpty()) {restrictions = cb.and(restrictions, cb.equal(path, condition.value));}} else {restrictions = cb.and(restrictions, cb.equal(path, condition.value));}}break;case ge:if (Number.class.isAssignableFrom(path.getJavaType()) && condition.value instanceof Number) {restrictions = cb.and(restrictions, cb.ge((Path<Number>) path, (Number) condition.value));}break;case gt:if (Number.class.isAssignableFrom(path.getJavaType()) && condition.value instanceof Number) {restrictions = cb.and(restrictions, cb.gt((Path<Number>) path, (Number) condition.value));}break;case lt:if (Number.class.isAssignableFrom(path.getJavaType()) && condition.value instanceof Number) {restrictions = cb.and(restrictions, cb.lt((Path<Number>) path, (Number) condition.value));}break;case ne:if (condition.value != null) {if (String.class.isAssignableFrom(path.getJavaType()) && condition.value instanceof String && !((String) condition.value).isEmpty()) {restrictions = cb.and(restrictions, cb.notEqual(path, condition.value));} else {restrictions = cb.and(restrictions, cb.notEqual(path, condition.value));}}break;case isNotNull:restrictions = cb.and(restrictions, path.isNotNull());break;}}return restrictions;}private Predicate getOrPredicates(Root<T> root, CriteriaBuilder cb) {// 相同的邏輯 Need TODOreturn null;}private List<javax.persistence.criteria.Order> getOrders(Root<T> root, CriteriaBuilder cb) {List<javax.persistence.criteria.Order> orderList = new ArrayList<>();if (root == null || CollectionUtils.isEmpty(orders)) {return orderList;}for (Order order : orders) {if (order == null) {continue;}String property = order.getProperty();Sort.Direction direction = order.getDirection();Path<?> path = this.getPath(root, property);if (path == null || direction == null) {continue;}switch (direction) {case ASC:orderList.add(cb.asc(path));break;case DESC:orderList.add(cb.desc(path));break;}}return orderList;}/*** 獲取Path** @param path Path* @param propertyPath 屬性路徑* @return Path*/private <X> Path<X> getPath(Path<?> path, String propertyPath) {if (path == null || StringUtils.isEmpty(propertyPath)) {return (Path<X>) path;}String property = StringUtils.substringBefore(propertyPath, PROPERTY_SEPARATOR);return getPath(path.get(property), StringUtils.substringAfter(propertyPath, PROPERTY_SEPARATOR));}/*** 條件*/public static class Cnd {Operator operator;String property;Object value;public Cnd(String property, Operator operator, Object value) {this.operator = operator;this.property = property;this.value = value;}/*** 相等** @param property* @param value* @return*/public static Cnd eq(String property, Object value) {return new Cnd(property, Operator.eq, value);}/*** 不相等** @param property* @param value* @return*/public static Cnd ne(String property, Object value) {return new Cnd(property, Operator.ne, value);}}/*** 排序*/@Getter@Setterpublic static class Order {private String property;private Sort.Direction direction = Sort.Direction.ASC;/*** 構(gòu)造方法** @param property 屬性* @param direction 方向*/public Order(String property, Sort.Direction direction) {this.property = property;this.direction = direction;}/*** 返回遞增排序** @param property 屬性* @return 遞增排序*/public static Order asc(String property) {return new Order(property, Sort.Direction.ASC);}/*** 返回遞減排序** @param property 屬性* @return 遞減排序*/public static Order desc(String property) {return new Order(property, Sort.Direction.DESC);}}/*** 運算符*/@Getter@Setterpublic enum Operator {/*** 等于*/eq(" = "),/*** 不等于*/ne(" != "),/*** 大于*/gt(" > "),/*** 小于*/lt(" < "),/*** 大于等于*/ge(" >= "), /*** 不為Null*/isNotNull(" is not NULL ");Operator(String operator) {this.operator = operator;}private String operator;} }7. 與之類似的解決方案還有 RSQL 的解決方案,可以參考Git_Hub上的此開源項目。
RSQL(RESTful Service Query Language)是 Feed Item Query Language (FIQL) 的超集,是一種 RESTful 服務(wù)的查詢語言。這里我們使用 rsql-jpa 來實踐,它依賴 rsql-parser 來解析 RSQL 語法,然后將解析后的 RSQL 轉(zhuǎn)義到 JPA 的 Specification。
maven 的地址如下:
<dependency><groupId>com.github.tennaito</groupId><artifactId>rsql-jpa</artifactId><version>2.0.2</version> </dependency>GitHub 文檔地址,詳見這里。如果要立志做優(yōu)秀的架構(gòu)師,Spring Data JPA 的實現(xiàn)還是非常好的,包括開源的生態(tài)等也非常好。
總結(jié)
以上是生活随笔為你收集整理的Spring Data JPA 从入门到精通~实际工作的应用场景的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL 字符集相关问题
- 下一篇: Spring Data JPA 从入门到