javascript
Spring Data JPA 从入门到精通~实际工作的应用场景
在實(shí)際工作中,有哪些場景會(huì)用到自定義 Repository 呢,這里列出幾種實(shí)際在工作中的應(yīng)用案例。
1. 邏輯刪除場景
可以用到上面說的兩種實(shí)現(xiàn)方式,如果有框架級(jí)別的全局自定義 Respository 那就在全局實(shí)現(xiàn)里面覆蓋默認(rèn) remove 方法,這樣就會(huì)統(tǒng)一全部只能使用邏輯刪除。但是一般是自定義一個(gè)特殊的刪除Respository,讓大家去根據(jù)不同的domain業(yè)務(wù)邏輯去選擇使用此接口即可。
2. 當(dāng)有業(yè)務(wù)場景要覆蓋 SimpleJpaRepository 默認(rèn)實(shí)現(xiàn)的時(shí)候
這種一般是具體情況具體分析的,一般實(shí)現(xiàn)特殊化的自定義 Respository 即可。
3. UUID 與 ID 的情況
經(jīng)常在實(shí)際生產(chǎn)中會(huì)有這樣的場景,對(duì)外暴露的是 UUID 查詢方法,而對(duì)內(nèi)呢暴露的是 Long 類型的 ID,這時(shí)候我們就可以自定義一個(gè) FindByIDOrUUID 的底層實(shí)現(xiàn)方法,在自定義的 Respository 接口里面。
4. 使用 Querydsl
Spring Data JPA 里面還幫我們做了 QuerydslJpaRepository 用來支持 Querydsl 的查詢方法,當(dāng)我們引入 Querydsl 的時(shí)候 Spring 就會(huì)自動(dòng)幫我們把 SimpleJpaRepository 的實(shí)現(xiàn)切換到 QuerydslJpaRepository 的實(shí)現(xiàn)。
5. 動(dòng)態(tài)查詢條件
由于 Data JPA 里面的 query method 或者 @query 注解不支持動(dòng)態(tài)查詢條件,正常情況下將動(dòng)態(tài)條件寫在 manager 或者 service 里面。這個(gè)時(shí)候如果是針對(duì)資源的操作,并且和業(yè)務(wù)無關(guān)的查詢的時(shí)候可以放在自定義 Repository 里面(有個(gè)缺點(diǎn)就是不能使用 SimpleJpaRepository,里面的很多優(yōu)秀的默認(rèn)是實(shí)現(xiàn)方法,在實(shí)際工作中還是放在 service 和 manager 中多一些,只是給大家舉個(gè)例子,知道有這么回事就行)。實(shí)例如下:
//我們假設(shè)要根據(jù)條件動(dòng)態(tài)查詢訂單 public interface OrderRepositoryCustom {Page<Order> findAllByCriteria(OrderCriteria criteria); // 定義一個(gè)訂單的定制化Repository查詢方法,當(dāng)然實(shí)際生產(chǎn)過程中,這里面可能不止一個(gè)方法。 }public class OrderRepositoryImpl implements OrderRepositoryCustom { @PersistenceContextEntityManager entityManager; /*** 一個(gè)動(dòng)態(tài)條件的查詢方法*/public List<Order> findAllByCriteria(OrderCriteria criteria) {// 查詢條件列表final List<String> andConditions = new ArrayList<String>();final Map<String, Object> bindParameters = new HashMap<String, Object>();// 動(dòng)態(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();}// 動(dòng)態(tài)創(chuàng)建queryfinal StringBuilder queryString = new StringBuilder();queryString.append("SELECT o FROM Order o");// 動(dòng)態(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();} } //實(shí)際中此種就比較少用了,大家知道有這么回事,真是遇到特殊場景必須要用了,可以用此方法實(shí)現(xiàn)。6. 擴(kuò)展 JpaSpecificationExecutor 使其更加優(yōu)雅
當(dāng)我們動(dòng)態(tài)查詢的時(shí)候經(jīng)常會(huì)出現(xiàn)下面的代碼邏輯,寫起來老是感覺有點(diǎn)不是特別優(yōu)雅,且有點(diǎn)重復(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);如果對(duì) Spring MVC 比較熟悉的話,可以更進(jìn)一步把其查詢提交和規(guī)則直接封裝到 HandlerMethodArgumentResolver 里面,把參數(shù)自動(dòng)和規(guī)則匹配起來。
我們可以對(duì)如下代碼進(jìn)行參考,感覺實(shí)現(xiàn)的還不錯(cuò),此段代碼可以作參考,只是實(shí)現(xiàn)的還有點(diǎn)不完整,如下:
/*** 擴(kuò)展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);}}/*** 運(yùn)算符*/@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上的此開源項(xiàng)目。
RSQL(RESTful Service Query Language)是 Feed Item Query Language (FIQL) 的超集,是一種 RESTful 服務(wù)的查詢語言。這里我們使用 rsql-jpa 來實(shí)踐,它依賴 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 的實(shí)現(xiàn)還是非常好的,包括開源的生態(tài)等也非常好。
總結(jié)
以上是生活随笔為你收集整理的Spring Data JPA 从入门到精通~实际工作的应用场景的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL 字符集相关问题
- 下一篇: Effective Java~45. 谨