В этой статье я покажу, как реализовать сортировку с аннотацией @OrderBy с Spring Data MongoDB .
Случай использования
Просто краткий пример того, что это такое для тех, кто раньше не использовал JPA @OrderBy. У нас есть два класса и отношение один ко многим:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
package pl.maciejwalkowiak.springdata.mongodb.domain;import org.bson.types.ObjectId;import org.springframework.data.annotation.Id;import org.springframework.data.mongodb.core.mapping.Document;@Documentpublic class Backpack { @Id private ObjectId id; private List<Item> items; ...} |
|
1
2
3
4
5
6
|
public class Item { private String name; private int price; ...} |
Рюкзак здесь является основным классом и содержит список встроенных предметов. Когда рюкзак загружается из базы данных, его элементы загружаются в порядке, близком к порядку вставки. Что если мы хотим изменить это и упорядочить элементы по одному из его полей? Нам нужно реализовать сортировку самостоятельно, и мы снова расширим AbstractMongoEventListener .
Сортировка деталей: введение @OrderBy
В отличие от JPA — сортировка в этом случае не может быть выполнена на уровне базы данных. Мы должны позаботиться об этом на стороне приложения — это можно сделать в двух местах:
- перед преобразованием объекта в структуру данных MongoDB — если мы хотим убедиться, что объекты правильно отсортированы в коллекции MongoDB
- после того, как объект преобразован из структуры данных MongoDB в объект Java — если мы просто хотим убедиться, что внутри нашего приложения список отсортирован правильно
Чтобы указать, в каком месте должна происходить сортировка, я создал перечисление SortPhase:
|
1
2
3
4
|
public enum SortPhase { AFTER_CONVERT, BEFORE_CONVERT;} |
Наконец, аннотация @OrderBy будет содержать три почти самоописываемых свойства:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
package pl.maciejwalkowiak.springdata.mongodb;import org.springframework.data.mongodb.core.query.Order;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.FIELD })public @interface OrderBy { /** * Field name */ String value(); Order order() default Order.ASCENDING; SortPhase[] phase() default SortPhase.AFTER_CONVERT;} |
Реализация SortingMongoEventListener
Декларативная сортировка должна использовать отражение. Чтобы код читался, я использовал commons-beanutils, но это можно было сделать вручную, не используя его. Добавьте следующие зависимости в ваш проект:
|
01
02
03
04
05
06
07
08
09
10
11
|
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version></dependency><dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version></dependency> |
Последняя часть — реализация SortingMongoEventListener :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
package pl.maciejwalkowiak.springdata.mongodb;import com.mongodb.DBObject;import org.apache.commons.beanutils.BeanComparator;import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;import org.springframework.data.mongodb.core.query.Order;import org.springframework.util.ClassUtils;import org.springframework.util.ReflectionUtils;import java.lang.reflect.Field;import java.util.Arrays;import java.util.Collections;import java.util.List;/** * MongoEventListener that intercepts object before its converted to BasicDBObject (before it is saved into MongoDB) * and after its loaded from MongoDB. * * @author Maciej Walkowiak */public class SortingMongoEventListener extends AbstractMongoEventListener { @Override public void onAfterConvert(DBObject dbo, final Object source) { ReflectionUtils.doWithFields(source.getClass(), new SortingFieldCallback(source, SortPhase.AFTER_CONVERT)); } @Override public void onBeforeConvert(Object source) { ReflectionUtils.doWithFields(source.getClass(), new SortingFieldCallback(source, SortPhase.BEFORE_CONVERT)); } /** * Performs sorting with field if: * <ul> * <li>field is an instance of list</li> * <li>is annotated with OrderBy annotation</li> * <li>OrderBy annotation is set to run in same phase as SortingFieldCallback</li> * </ul> */ private static class SortingFieldCallback implements ReflectionUtils.FieldCallback { private Object source; private SortPhase phase; private SortingFieldCallback(Object source, SortPhase phase) { this.source = source; this.phase = phase; } public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { if (field.isAnnotationPresent(OrderBy.class)) { OrderBy orderBy = field.getAnnotation(OrderBy.class); if (Arrays.asList(orderBy.phase()).contains(phase)) { ReflectionUtils.makeAccessible(field); Object fieldValue = field.get(source); sort(fieldValue, orderBy); } } } private void sort(Object fieldValue, OrderBy orderBy) { if (ClassUtils.isAssignable(List.class, fieldValue.getClass())) { final List list = (List) fieldValue; if (orderBy.order() == Order.ASCENDING) { Collections.sort(list, new BeanComparator(orderBy.value())); } else { Collections.sort(list, new BeanComparator(orderBy.value(), Collections.reverseOrder())); } } } }} |
Чтобы использовать его, вам просто нужно объявить этот класс как bean-компонент Spring в контексте приложения:
|
1
|
<bean class="pl.maciejwalkowiak.springdata.mongodb.SortingMongoEventListener" /> |
пример
Теперь пришло время добавить созданную аннотацию OrderBy в класс Backpack с начала этого поста. Допустим, мы хотим упорядочить товары по цене в порядке убывания:
|
01
02
03
04
05
06
07
08
09
10
|
@Documentpublic class Backpack { @Id private ObjectId id; @OrderBy(value = "price", order = Order.DESCENDING) private List<Item> items; ...} |
Это оно. Теперь каждый раз, когда вы загружаете объекты рюкзака — не имеет значения, если его findAll, findOne или ваш собственный метод — элементы в рюкзаке будут упорядочены.
Резюме
SortingMongoEventListener — еще один пример мощной системы событий Spring Data MongoDB. Пожалуйста, прокомментируйте и дайте мне знать, если вы считаете, что эта функция может быть частью Spring Data MongoDB.
Ссылка: Сортировка коллекций Spring Data MongoDB с использованием @OrderBy от нашего партнера по JCG Мачея Уоковяка в блоге « Путь к разработке программного обеспечения» .