4

Long story short, I am using JDBI DAO to access data. It is hard to have a query with a dynamic order in it. So I am planning to use a Java 8 stream to perform this as a post processing step after the query results are fetched.

The problem is the way the comparator works by having to declare a method of the object statically.

shepherds = shepherds.stream()
    .sorted(Comparator.comparing(Shepherd::getId).reversed())
    .collect(Collectors.toList());

How can I do this dynamically with what will be variables like this

orderBy = id
orderDirection = ASC

So that I can paramaterize this method call?

e.g.

if(orderDirection.equals("ASC"))
    shepherds.stream().sorted(Comparator.comparing(orderBy));
else
    shepherds.stream().sorted(Comparator.comparing(orderBy).reversed());
Michael
  • 41,989
  • 11
  • 82
  • 128
tomaytotomato
  • 3,788
  • 16
  • 64
  • 119
  • 1
    Since streams didn't "sort out" your original problem, you might just as well forget about it. Sort in the database, and if you can't get JDBI to bend that way, do it in a different way. – Kayaman Feb 05 '18 at 10:36
  • The fact that you use a `static`access to the sorter instance does not mean that you (have to) declare the comparator *statically*. – Timothy Truckle Feb 05 '18 at 10:39
  • 2
    Hey, the title might be talking about sorting with streams, but the actual problem is sorting with JDBI, for which there is a solution in the [duplicate](https://stackoverflow.com/questions/16790529/dynamic-order-in-jdbi-sql-object-queries). Just because you don't know how to do A, doesn't mean you should do it poorly with B. – Kayaman Feb 05 '18 at 10:41
  • Besides the what the other comments suggest (I would recommend that too), you could have a look here (if you absolutely want to do the sorting on a code level): https://commons.apache.org/proper/commons-beanutils/apidocs/org/apache/commons/beanutils/BeanComparator.html – JDC Feb 05 '18 at 10:45
  • Yea I had a look at that, its hacky and makes me vulnerable to SQL injections. No thanks :) JDBI is not a mature solution I think – tomaytotomato Feb 05 '18 at 10:47
  • 3
    You should always sort in database. It's not worth to have to stream whole result set into your app via (potentially) http, just to "find out" that original sorting was wrong, and you need only the very last ten rows. This does not scale. At all. – M. Prokhorov Feb 05 '18 at 11:06
  • While the approach might in general be vulnerable to SQL injection, it's not too hard to mitigate that by checking the parameters for valid values (like `ASC`, `DESC` etc.). I'm not sure why you'd say JDBI isn't mature, but I suspect you don't really have the qualifications to determine that (and why are you using it if you don't think it's mature). As you can see everyone is recommending sorting in the database. – Kayaman Feb 05 '18 at 11:36
  • If it doesn't provide functionality to do ordering despite being in a third major release, I would say they still have a long way to go. However Raw SQL is fashionable now and I am sticking with it rather than use horrible Hibernate – tomaytotomato Feb 05 '18 at 11:55
  • The discussion about the database beside, instead of boilerplate for reverse order just do `Comparator.comparing (-orderBy)` if you need it. – user unknown Feb 05 '18 at 11:59
  • I'm not sure what you mean by "doesn't provide functionality", since it does provide it. I also have no idea how you managed to bring in Hibernate, when it was never discussed. Now you're "sticking" with raw SQL even though you were using JDBI, which was the point of this question? Sounds like someone has a case of "I don't know what I'm doing, but it sure makes me angry". – Kayaman Feb 05 '18 at 12:15
  • Mostly confused but I will make it work, it has to work. – tomaytotomato Feb 05 '18 at 12:19
  • Consider [Sort a list of objects based on a parameterized attribute of the object](https://stackoverflow.com/q/45550934/2711488) or [Sorting a collection in a generic method in Java 8](https://stackoverflow.com/q/33084560/2711488)… – Holger Feb 05 '18 at 13:40

1 Answers1

2

The simplest way is to probably build a Map (unless you really can't do that on the DB side, which should be your main focus), where the Key would look like :

class Key {
    String field;
    Direction direction; // enum
    // getters/setters/hashcode/equals
}

And simply create this Map upfront:

Map<Key, Comparator<Shepherd>> map = new HashMap<>();
map.put(new Key("id", Direction.ASC), Comparator.comparing(Shepard::getId))
map.put(new Key("id", Direction.DESC), Comparator.comparing(Shepard::getId).reversed())

The other way, I think it would be possible to create this really dynamically via LambdaMetafactory, but it is rather complicated.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    It might be cleaner if you store only the field as the key, use it to retrieve the comparator, then if the direction is DESC, call reversed on the comparator. This way you don't need the class Key at all and you reduce the number of items in the map by half. – Alexandru Severin Feb 05 '18 at 11:10
  • @AlexandruSeverin right, you can do that also. I would not worry about space here, but I would still create the `Key` class, so that the retrieval is fast (`O(1)`), without any additional post-processing (if/else) later. But, of course, you make sense too – Eugene Feb 05 '18 at 11:15
  • 2
    Instead of the `Key` class, you could simply use `Array.asList("id", Direction.ASC)` etc. or even better, `List.of("id", Direction.ASC)` when you’re use Java 9. – Holger Feb 05 '18 at 12:39
  • I'd also use `Comparator super Shepherd>` as the type of the values, so that compators that compare super types of `Shepherd` could also be used. – fps Feb 05 '18 at 13:28
  • @FedericoPeraltaSchaffner right, even if I never used in real code, probably never hand an example where this was needed; but good point yes – Eugene Feb 05 '18 at 13:29