Jinq
Jinq copied to clipboard
java.io.NotSerializableException trying to run a simple query
With the following code:
@Entity
public class Product {
private String sku;
...
public Product findSkuDuplicate(ProductRepository repository) {
Product existing = repository.all()
.where(product -> product.sku.equals(sku))
.getOnlyValue();
return existing != null && existing != this ? existing : null;
}
...
}
I get the following error:
[java.lang.IllegalArgumentException: Could not extract code from lambda. This error sometim
es occurs because your lambda references objects that aren't Serializable.] with root cause
java.io.NotSerializableException: org.teavm.flavour.example.model.Product
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.user00.thunk.SerializedLambda.extractLambda(SerializedLambda.java:52)
at org.jinq.jpa.transform.LambdaInfo.analyze(LambdaInfo.java:30)
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:364)
at org.jinq.jpa.JPAQueryComposer.where(JPAQueryComposer.java:58)
at org.jinq.orm.stream.QueryJinqStream.where(QueryJinqStream.java:45)
at org.jinq.jpa.QueryJPAJinqStream.where(QueryJPAJinqStream.java:86)
at org.jinq.jpa.QueryJPAJinqStream.where(QueryJPAJinqStream.java:12)
at org.teavm.flavour.example.model.Product.findSkuDuplicate(Product.java:80)
I can alter this code to refer to temporary variable that holds this.sku
, but I am still interested why do you need to serialize lambdas?
Correct. You need to create a local variable to hold this.sku
. Also, Jinq expects all accesses to fields to happen through getter methods, so instead of
.where(product -> product.sku.equals(sku))
you would need to write something like
.where(product -> product.getSku().equals(sku))
The serialization is necessary because Java 8 doesn't provide a reflection mechanism that can be used by Jinq to determine which lambda it received. When Jinq receives a lambda, it can't use instanceof
or getClass()
or anything else to see what it is. The only way to get that information out is to serialize the lambda. Java will then write out the name of the lambda in the serialized output.
I describe the issue in more detail in my JVM language summit talk (there's a link in the Jinq documentation section).
The serialization is necessary because Java 8 doesn't provide a reflection mechanism that can be used by Jinq to determine which lambda it received. When Jinq receives a lambda, it can't use
instanceof
orgetClass()
or anything else
I see. LambdaMetafactory really does not provide anything about dynamic call site. Is it possible to pass this information using class transformation? For example, create you own bootstrap method, which creates lambda class with corresponding metadata and rewrite all occurences of LambdaMetafactory.metafactory with this new method?
Also, Jinq expects all accesses to fields to happen through getter methods, so instead of
I prefer rich DDD style, so fields are nothing more than implementation details for me. I have some scenarios when I have field and don't provide direct getters/setters for it. Why there is such constraint in Jinq?
Well, it was an intentional decision by the Java 8 people to design lambdas in such a way that you can't use reflection on them to figure out what a lambda pointer refers to. It is theoretically possible to design a bytecode rewriter that rewrites all lambda operations to use classes instead or to maintain a weak hash map or something that holds metadata for every lambda created, but it's a bit of a mess and fragile.
Why there is such constraint in Jinq?
Just a lack of time. It's certainly possible to have Jinq translate direct field accesses into queries. But most people who use Hibernate and EclipseLink etc. do use getter methods (in fact, some configurations of JPA require you to use getter methods). It's easy to workaround the problem since it's not that hard for people to add getter methods to their entities. And I think it's considered best practice to use getter methods instead of direct field access for both JPA and for Java in general (though, I might be mixing things up with Smalltalk). As a result, no one has actually needed support for that, so I haven't tried to find the time to implement it.
I'll put it on the list of things to implement in the future. If it's actually urgent, I can try to prioritize it.
Thank you. This is not urgent.
As for people who define getters without thinking. They probably break encapsulation. Imagine someone who designs collection library and provides direct access of backing hashtable of HashMap class. The same thing with entities: sometimes it reasonable to prevent direct access to a field from outside, and public methods may use these fields somehow.