Sorting Spring Data MongoDB collections using @OrderBy

This is already third post about tuning and enhancing Spring Data MongoDB capabilities. This time I found that I miss one JPA feature – @OrderBy annotation. @OrderBy specifies the ordering of the elements of a collection valued association at the point when the association is retrieved.

In this article I will show how to implement sorting with @OrderBy annotation with Spring Data MongoDB.  

Use case

Just a short example of what is it all about for those who did not use JPA @OrderBy before. We’ve got here two classes and one to many relation:

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;

 ...
}
public class Item {
 private String name;
 private int price;

 ...
}

Backpack is here a main class and contains list of embedded items. When Backpack is loaded from database its items are loaded in order close to insertion order. What if we want to change that and order items by one of its fields? We need to implement sorting by our own and again we will extend AbstractMongoEventListener.

Sorting details: introducing @OrderBy

In opposite to JPA – sorting in this case sorting cannot be done on database level. We need to take care about it on application side – that can be done in two places:

  • before object is converted into MongoDB data structure – if we want to make sure that objects are sorted properly inside MongoDB collection
  • after object is converted from MongoDB data structure into Java object – if we just want to make sure that inside our application List is sorted properly

In order to specify in which place sorting should take place I have created SortPhase enumeration:

public enum SortPhase {
 AFTER_CONVERT,
 BEFORE_CONVERT;
}

Finally – @OrderBy annotation will contain three almost self describing properties:

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;
}

Implementing SortingMongoEventListener

Declarative sorting has to use reflection. To keep code readable I used commons-beanutils but it could have been done manually without using it. Add following dependencies to your project:

<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>

The final part is SortingMongoEventListener implementation:

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()));
    }
   }

  }
 }
}

In order to use it you just need to declare this class as a Spring bean in application context:

<bean class="pl.maciejwalkowiak.springdata.mongodb.SortingMongoEventListener" />

Example

Now its time to add created OrderBy annotation to Backpack class from beginning of this post. Lets say we want to order items by price in descending order:

@Document
public class Backpack {
 @Id
 private ObjectId id;

 @OrderBy(value = "price", order = Order.DESCENDING)
 private List<Item> items;

 ...
}

Thats it. Now every time you load Backpack objects – does not matter if its findAll, findOne or your custom method – items in backpack will be ordered.  

Summary

SortingMongoEventListener is another example how Spring Data MongoDB event system is powerful. You are welcome to comment and let me know if you think this feature could be a part of Spring Data MongoDB.

Reference: Sorting Spring Data MongoDB collections using @OrderBy from our JCG partner Maciej Walkowiak at the Software Development Journey blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


+ 4 = thirteen



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close