Skip to content

Java Language Support

Konstantin Triger edited this page May 1, 2020 · 6 revisions

JRE version support: Java Version

Most of syntax is supported. Easier to list what is not:

  • Conditional logic (if or ?:) - use CASE\WHEN or IIF instead.
  • Loops (for or while), since there is no corresponding SQL construct. See Dynamic Queries for dynamic scenarios.
  • switch statement.
  • try/catch/finally constructs.
  • Some operators. FluentJPA translates most operators "as is", therefore if your database does not support e.g. >> operator, you cannot use it.

Functions that can be called are either:

Custom Functions and Variables

Just works with a small constraint: functions must be either static or interface default. Once your function meets the Java Language Support standards above, you can have them as many as you want; call one another etc.
Example (refactoring the Builtin Mappings):

public List<X> myMethod(List<String> names) {
    FluentQuery query = FluentJPA.SQL((Table1 t1) -> {

        //custom function call
        customFunction(t1, names);

        // variables and expressions can be used here as well
    });
    ...
}

// custom function - ordinary Java function...
private static void customFunction(Table1 t1, List<String> names) {
    selectAll(t1);                                       // nested custom function call
    boolean containsName = names.contains(t1.getName()); // variable
    WHERE(t1.getP1() + 5 < t1.getP2() || containsName);  // type-safe expression
                                                         // of any complexity
}

Understand what it mean from SQL translation perspective: since there is no general concept of a "local" function in SQL, FluentJPA will duplicate the expression produced by the function (or stored in a variable) as many times as it is used. If the function accepts parameters, the produced expressions will be different with regard to the arguments.

Parameters

Just works. Simply use parameters inside your code and FluentJPA will do the rest - identify them and register using Query.setParameter(). See an example with names parameter in Builtin Mappings below.

Note, FluentJPA does not differentiate between parameters and local variables declared before the call to FluentJPA.SQL() and used inside it.

Builtin Mappings

To enhance Java integration, there are several Java methods/constructs mapped to the SQL counterparts:

  • String concatenation: expression like x.firstName() + " " + x.lastName() will be converted to corresponding CONCAT SQL function call.
  • Collection.contains(): mapped to IN operator. Works on any type implementing Collection interface. Especially useful in the following scenarios:
    • Collection parameters, e.g.

      public List<X> myMethod(List<String> names) {
          FluentQuery query = FluentJPA.SQL((Table1 t1) -> {
      
              selectAll(t1);
              WHERE(names.contains(t1.getName());
              //    ^^^^^^^^^^^^^^
              // will be translated to something like `t1.name IN (?1)`.
              // But looks more natural with Java `contains`
          });
          ...
      }
    • WHERE clause with sub-select involving 2 tables, e.g.

      FluentQuery query = FluentJPA.SQL((Table1 t1,
                                         Table2 t2) -> {
      
          selectAll(t1);
          WHERE(collect(t2, t2.getCol1()).contains(t1.getCol1()));
          //    ^^^^^^^ collect using Library, and then contains
      });
    • Collection.size() and Collection.isEmpty() are mapped to respective COUNT and EXISTS constructs.

    • String.concat(), String.length(), String.toLowerCase(), String.toUpperCase(), String.replace(), String.trim() - all mapped to respective SQL functions.

    • Most Math.* methods are mapped as well.

Custom Mappings

You can register a custom mapping between any Java method and a custom or mapped function. So any time FluentJPA encounters a call to that Java method, it will "call" the mapping instead. In fact all the Builtin Mappings above are mapped by this technique. E.g.:

// substitute String::concat with mapped ScalarFunctions::CONCAT
FluentJPA.SQLConfig().registerMethodSubstitution(String::concat, ScalarFunctions::CONCAT);

// substitute Collection::contains with a custom function (lambda)
// that invokes a mapped operator IN.
// This mapping "powers" the examples above
FluentJPA.SQLConfig().registerMethodSubstitution(
          (Function2<Collection<Comparable>, Comparable, Boolean>)
                                             Collection::contains,
                                             (collection, item) -> IN(item, collection));