В этой статье я покажу, как реализовать сортировку с аннотацией @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; @Document public 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
|
@Document public 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 Мачея Уоковяка в блоге « Путь к разработке программного обеспечения» .