2

My question is about Jinq , and I am using version 1.8.9 which currently is the latest release.

I am trying to use Jinq for implementing a general reusable JPA (Java Persistence API) typesafe query method with the Java 8 lambda (functional interface) Predicate as method parameter.

Unfortunately, I can not make it work with the Java 8 Predicate but instead I can use a similar predicate type (provided by Jinq) as method parameter, but would like to avoid dependencies to Jinq in method signatures, and therefore would prefer the Java 8 Predicate if possible?

Jinq provides the functional interface "Where":

package org.jinq.orm.stream;
public interface JinqStream<T> extends Stream<T> {
    @FunctionalInterface
    public static interface Where<U, E extends Exception> extends Serializable {
      public boolean where(U obj) throws E;
    }

I can implement the query method I want (but with the undesirable coupling) by using the above interface in the method signature like this:

public List<T> select(JinqStream.Where<T, Exception> wherePredicate)

Instead of the above coupling to Jinq in the method signature I would like to use the standard Predicate as below:

public List<T> select(java.util.function.Predicate<T> wherePredicate) {

The standard Predicate is defined like this:

@FunctionalInterface
public interface Predicate<T> {
  public boolean test(T t);
}

Therefore I thought it might have been possible to implement my desired select method using the following code for creating a lambda implementation of the Jinq interface:

public List<T> select(java.util.function.Predicate<T> predicate) {
    org.jinq.orm.stream.JinqStream.Where<T, Exception> wherePredicate = u -> predicate.test(u);
    ...

However, it does not work but results in an IllegalArgumentException (see stacktrace pasted further down below)

Below is some more code illustrating what I am trying to do.

The problem I am trying to illustrate is that I want to use the Predicate parameter in the below method "DataMapperBase.select2" instead of the Jinq specific Where parameter as in below method "DataMapperBase.select".

public abstract class DataMapperBase<T> {
    ...
    private EntityManagerFactory entityManagerFactory;
    private EntityManager entityManager;
    private final Class clazz;// initialized using below method getClazz()

    private Class getClazz() throws ClassNotFoundException {
        Type genericSuperclass = getClass().getGenericSuperclass();
        Type actualTypeArgument = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0];
        return Class.forName(actualTypeArgument.getTypeName());
    }

    // This method works but has an undesirable dependency to Jinq in method signature.
    public List<T> select(org.jinq.orm.stream.JinqStream.Where<T, RuntimeException> wherePredicate) {
        JinqJPAStreamProvider streams = new JinqJPAStreamProvider(entityManagerFactory);
        List<T> result = streams
          .streamAll(entityManager, clazz)
          .where( wherePredicate )
          .toList();
        return result;
    }    

    // Instead of the above select method I want to use the below method (currently named "select2")

    // The code below compiles but does not work in runtime.This is the method signature I would like to use.
    public List<T> select2(java.util.function.Predicate<T> predicate) {
        org.jinq.orm.stream.JinqStream.Where<T, RuntimeException> wherePredicate = u -> predicate.test(u);    
        JinqJPAStreamProvider streams = new JinqJPAStreamProvider(entityManagerFactory);
        List<T> result = streams
          .streamAll(entityManager, clazz)
          .where( wherePredicate )
          .toList();
        return result;
    }
...
public class PersonDataMapper extends DataMapperBase<Person> { ...
...
@Entity
@Access(AccessType.PROPERTY)
@Table(name="person")
public class Person implements Serializable {
    ...
    private int age;
    @Column(name = "Age")
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    ...

...
// The invocations below can be used e.g. from a test class
List<Person> persons  = personDataMapper.select( p -> p.getAge() > 20 );
List<Person> persons2 = personDataMapper.select2( p -> p.getAge() > 20 );

Both above methods (select and select2) compiles but the second fails in runtime with the following exception;

java.lang.IllegalArgumentException: Could not extract code from lambda. This error sometimes occurs because your lambda references objects that aren't Serializable.
    at org.jinq.jpa.transform.LambdaInfo.analyze(LambdaInfo.java:33)
    at org.jinq.jpa.transform.LambdaAnalysisFactory.extractSurfaceInfo(LambdaAnalysisFactory.java:7)
    at org.jinq.jpa.JPAQueryComposer.applyTransformWithLambda(JPAQueryComposer.java:269)
    at org.jinq.jpa.JPAQueryComposer.where(JPAQueryComposer.java:365)
    at org.jinq.jpa.JPAQueryComposer.where(JPAQueryComposer.java:1)
    at org.jinq.orm.stream.QueryJinqStream.where(QueryJinqStream.java:45)
    at org.jinq.jpa.QueryJPAJinqStream.where(QueryJPAJinqStream.java:86)

The error message indicates it might be a problem with java.util.function.Predicate not implementing Serializable. (since the Person in my example implements Serializable)

Though, then I experimented with another interface like this:

public interface Predicate2<T> extends java.util.function.Predicate<T> , Serializable {}

When I used it instead, I got the following exception:

java.lang.IllegalArgumentException: Could not analyze lambda code
    at org.jinq.jpa.transform.LambdaAnalysis.fullyAnalyzeLambda(LambdaAnalysis.java:197)
    at org.jinq.jpa.transform.LambdaInfo.fullyAnalyze(LambdaInfo.java:116)
    at org.jinq.jpa.JPAQueryComposer.applyTransformWithLambda(JPAQueryComposer.java:278)
    at org.jinq.jpa.JPAQueryComposer.where(JPAQueryComposer.java:365)
    at org.jinq.jpa.JPAQueryComposer.where(JPAQueryComposer.java:1)
    at org.jinq.orm.stream.QueryJinqStream.where(QueryJinqStream.java:45)
    at org.jinq.jpa.QueryJPAJinqStream.where(QueryJPAJinqStream.java:86)    

So, my question is if someone can provide a working implementation of the above method "DataMapperBase.select2" i.e. the method using parameter java.util.function.Predicate ?

Pelle
  • 137
  • 2
  • 6

1 Answers1

3

Due to limitations of Java 8's implementation of lambdas, Jinq requires the use of Serializable lambdas. Unfortunately, the default Predicate in Java 8 is not Serializable, so it cannot be analyzed by Jinq.

I gave a talk at the JVM Language Summit last year on how Jinq works. The section on why the default Java 8 Predicate couldn't be used by Jinq is discussed at around the 17:16 mark:

https://youtu.be/JqCnZFzTR2I?t=17m16s

The current Jinq bytecode analysis is currently designed to work mainly with the Jinq query style, so if you try to stuff in arbitrary lambdas, Jinq's analysis will likely fail. That's also discussed elsewhere in the video.

Ming-Yee Iu
  • 844
  • 6
  • 5