I would like to ask what people think about the following idea. There
could be an annotation that generates an implementation of a JPA
finder, or a named query. E.g. for an auto-generated named query, I
would put the following to an entity class:
@Entity
@AutoNamedQueries({
@AutoNamedQuery("Customer.findAll"),
@AutoNamedQuery("Customer.findByCustomerId"),
@AutoNamedQuery("Customer.findByZip")
})
public class Customer {
...
}
After transformation, it would generate the following:
@Entity
@NamedQueries({
@NamedQuery(name = "Customer.findAll", query = "SELECT c FROM
Customer c"),
@NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c
FROM Customer c WHERE c.customerId = :customerId"),
@NamedQuery(name = "Customer.findByZip", query = "SELECT c FROM
Customer c WHERE c.zip = :zip")
})
public class Customer {
...
}
I.e. the actual implementation of the query would be automatically
generated based on the query name.
For finders, one would write the following in a DAO (facade) class:
@AutoFinders(forEntity=Pet.class, finders={
@AutoFinder("findByNameAndWeight")
@AutoFinder("findByOwner")
@AutoFinder("findByTypeAndNameLike")
})
public class PetFacade {
...
}
This would generate the following methods in the PetFacade class,
including the complete implementation:
public class PetFacade {
public List<Pet> findByNameAndWeight(String name, Float weight) {
Query q = em.createQuery("SELECT Pet FROM Pet AS pet WHERE
pet.name = :name AND pet.weight = :weight");
q.setParameter("name", name);
q.setParameter("weight", weight);
return q.getResultList();
}
public List<Pet> findByOwner(Owner owner) {
Query q = em.createQuery("SELECT Pet FROM Pet AS pet WHERE
pet.owner = :owner");
q.setParameter("owner", owner);
return q.getResultList();
}
public List<Pet> findByTypeAndNameLike(PetType type, String name) {
name = name.replace('*', '%');
if (name.charAt(0) != '%') {
name = "%" + name;
}
if (name.charAt(name.length() -1) != '%') {
name = name + "%";
}
Query q = em.createQuery("SELECT Pet FROM Pet AS pet WHERE
pet.type = :type AND pet.name LIKE :name");
q.setParameter("type", type);
q.setParameter("name", name);
return q.getResultList();
}
}
Again, the implementation would be created based on the finder name
itself. As you see, the finder name pattern does not need to be
limited only to single column name, but it could understand the
combination of column names, logical operators, qualifiers such as
LessThan, NotEqual, Between, Like etc. Also, it could also generate a
variant of the finder with int firstResult, int maxResults parameters,
resulting in something like:
public List<Person> findAll(int firstResult, int maxResults) {
return em.createQuery("select o from Person o").
setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
}
(BTW, the above examples use the Java Persistence Query Language, but
it could just as well generate Criteria API calls that were added in
JPA 2.0.)
This idea is inspired by the dynamic finders in Grails, which work in
a similar way:
http://www.grails.org/doc/1.1.x/guide/single.html#5.4.1%20Dynamic%20Finders
I am attaching a skeleton of the AutoFinder and AutoNamedQuery
annotations and the corresponding Javac handlers (just a first sketch
and a proof of concept, does not do much yet). The attached
annotations also use a slightly different syntax than outlined above.
What do you think about this application of Lombok?
Thanks,
Petr
But for the finder, I'm not sure if it's such a big hit, because it's
raw JPA-specific.
Most server-side apps (at least the ones I've seen) are Spring-based,
so you would be probably using the Spring JpaTemplate class for all
the finder logic.
Hence, your EntityManager-specific code may not be applicable for many
apps...
I'll split up my thoughts into two replies. First, on JPA boilerplate
in particular, and then (as I'll be concluding this is best left to
lombok plugins), on how to distribute lombok plugins better than the
current situation.
But first, JPA boilerplate:
I'm not entirely certain lombok is the right place for this stuff; the
sheer amount of interesting features that have been paraded on this
newsgroups as being 'boilerplatey' in nature suggests to me what's
needed instead is some sort of DSL, preferably one that gets picked up
automatically by the java compiler.
There's so much effort going on in this space, it's hard to come up
with something for lombok now that'll stand the test of time. For
example, I know Alex Buckley had quite a bit to say during the devoxx
BOF on java7 features about a JVM 'meta compiler' capable of compiling
any number of source files written in any number of source languages
all at once, solving interdependencies between different source files,
even if written in different languages, automatically. If something
like that existed and/or javac was extended with a feature for other
language compilers to 'plug in', generate dependency-free TypeMirror-
like objects in a first round, and actually compile on the second,
then such a DSL could be written directly, and with mostly the same
benefits as lombok annotations: it "just works", seamlessly, in all
your build processes and presumably the TypeMirror concept is simple
enough to generate so that IDEs can work with this the way they work
with java code: As-you-type, continuously integrated, with
dependencies sorted out automatically.
That's the utopia vision. The fact that Annotation Processing
integration in editors is pretty bad (I hope lombok is making some
impact on rectifying this, but still), and that API has been around
for quite a while at this point. It's also fairly well designed. There
are some issues about performance (the Annotation Processing API has
full resolution available, and doing full resolution on an enormous
source tree is just not feasible), but at the very least run-
processors-on-save is performance-wise perfectly doable and only
eclipse does this, and not particularly well.
Still, even if that is as of now still a pipe dream, it contrasts
rather sharply to what the situation would be if lombok offered these
features. With lombok, you'd have iffy resolution getting in the way
(thank eclipse for that), and you'd have to express the DSL entirely
in annotations.
One could argue that JPA annotations already are a DSL. If I
understand JPA correctly, you are desugaring a bunch of annotations
into a bunch of different, more verbose annotations. Using lombok to
do this feels like the wrong place for a much simpler reason: Why not
add @AutoNamedQuery to JPA itself? It's hard to change such a
widespread established standard as JPA, but I see nothing backwards
incompatible about doing this; it can even be implemented in the
generic JPA interface layer without burdening JPA implementors with
the need to add it.
Don't get me wrong; even with the kludge of using annotations to write
something that has ceased to look anything like a simple POJO,
@AutoNamedQuery is pretty cool!
Going back to DSLs, given that Lombok is already plugged into a
relatively early step in the compilation process, why doesn't lombok
extend into this territory? I'm not entirely sure Lombok's current
javac deployment mechanism runs early enough to catch files that look
absolutely nothing like java, but perhaps we can find a way. At worst
we'll have to resort to the simple but hacky 'magic comment' strategy.
However this happens, one thing is fairly clear to me: JPA's needs are
very specific and almost always unsuitable for anywhere else. For
example, a recurring issue that we keep flagging with 'WontFix' in the
issue tracker is to make the equalsAndHashCode implementation use an
instanceof check instead of a straight class comparison. This is the
right thing to do for JPA (due to proxy classes). It's the wrong thing
to do anywhere else (because you can't guarantee transitivity that
way). Thus, while I confess I haven't used JPA in any real world
project, so I might be way off here, it seems to me that lombok-based
JPA boilerplate busting annotations should be very clearly marked as
being useful only in combination with JPA.
Transformations that make sense only in the context of a library
should probably not be piled into the core lombok distribution -
though JPA may be popular enough to break this rule, especially if we
create a special lombok.jpa package for it. To decide what to do, it
helps to know how easy it is to plug into lombok. Which I'll deal with
in the next post (I'll start a new topic for that).
thanks a lot for your elaborate and insightful reply.
See more below.
With respect to NetBeans, this is a very hot topic for the upcoming
NetBeans 6.9: http://wiki.netbeans.org/
EditorPlan69#Annotations_support. We can expect some new development
in this area in the coming weeks and months.
>
> Still, even if that is as of now still a pipe dream, it contrasts
> rather sharply to what the situation would be if lombok offered these
> features. With lombok, you'd have iffy resolution getting in the way
> (thank eclipse for that), and you'd have to express the DSL entirely
> in annotations.
>
>
> One could argue that JPA annotations already are a DSL. If I
> understand JPA correctly, you are desugaring a bunch of annotations
> into a bunch of different, more verbose annotations. Using lombok to
> do this feels like the wrong place for a much simpler reason: Why not
> add @AutoNamedQuery to JPA itself? It's hard to change such a
> widespread established standard as JPA, but I see nothing backwards
> incompatible about doing this; it can even be implemented in the
> generic JPA interface layer without burdening JPA implementors with
> the need to add it.
The truth is that JPA as a technology *is* quite 'boilerplatey'. There
is a lot of code that people have to repeat over and over when
creating data access objects. This code should be simplified away. But
you have a good point that Lombok may not be the best approach to
address this, and the implementation of things like @AutoNamedQuery
could/should just as well be addressed at the level of JPA, or an
independent library on top of JPA.
When I searched the web, I found out that a couple projects indeed
already try to do that, such as this one: http://redmine.synyx.org/projects/show/hades
. The first thing to do is to abstract common runtime code to a
library or the standard itself, and then we'll see whether there is
something left for Lombok to do.
I agree that many things would be much easier if it was possible to
integrate them at the language level, and if we had an even much
better (meta)compiler than javac is today, but as you say, this is the
utopia. Lombok should be focused on practical problems that can be
addressed right now using today's tools, not on problems we may be
able to solve using technologies that will be ready in 2-3 years.
I agree any support for JPA should not be at the very heart of Lombok
- it should be a separate extension.
Petr
>
>
>
> On Dec 18, 9:20 pm, Jacek Furmankiewicz <jace...@gmail.com> wrote:
>> For JPA entities, anything that helps is good. Especially the
>> cumbersome syntax for mapping an @Id to a Oracle sequence (very
>> convoluted for something that should be so simple).
>>
>> But for the finder, I'm not sure if it's such a big hit, because it's
>> raw JPA-specific.
>> Most server-side apps (at least the ones I've seen) are Spring-based,
>> so you would be probably using the Spring JpaTemplate class for all
>> the finder logic.
>>
>> Hence, your EntityManager-specific code may not be applicable for
>> many
>> apps...
>
> --
> You received this message because you are subscribed to the Google
> Groups group for http://projectlombok.org/
>
> To post to this group, send email to project...@googlegroups.com
> To unsubscribe from this group, send email to
> project-lombo...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/project-lombok?hl=en
> For JPA entities, anything that helps is good. Especially the
> cumbersome syntax for mapping an @Id to a Oracle sequence (very
> convoluted for something that should be so simple).
>
Hi Jacek,
I must say I don't have hands on experience with JPA with Oracle
database, so I don't know what you are referring to. This page does
not suggest that this is painful for cumbersome: http://wiki.eclipse.org/EclipseLink/Examples/JPA/PrimaryKey
Can you please elaborate on what you mean?
> But for the finder, I'm not sure if it's such a big hit, because it's
> raw JPA-specific.
Again, I am not sure what you mean - to me it seems auto-generating
JPQL/Criteria queries could be perfectly applicable also to Spring
apps, no? In fact, the framework I just mentioned in my previous mail: http://redmine.synyx.org/projects/show/hades
simplifies building queries in the context of Spring. In Spring, you
still have to write the query today, and this part could be
simplified, right?
> Most server-side apps (at least the ones I've seen) are Spring-based,
> so you would be probably using the Spring JpaTemplate class for all
> the finder logic.
>
> Hence, your EntityManager-specific code may not be applicable for many
> apps...
Sure, the implementation may need to be specific to several
environements, such as Spring, JavaEE6 (EJB 3.1, CDI), Seam, ..., but
the concepts are the same, I believe.
Petr
a) do it manually on the server-side via GUIDs
b) using autoincrement columns (most DBs support those). That is easy
in JPA
c) for Oracle (and PostgreSQL too I guess) use sequences.
However, the syntax for defining a sequence as a generator for a PK is
very verbose in JPA, e.g.:
@Id @Column(name = "F_SUBSCRIBER_PK")
@GeneratedValue
(strategy=GenerationType.SEQUENCE,generator="S_SUBSCRIBER")
@SequenceGenerator(name="S_SUBSCRIBER",sequenceName="S_SUBSCRIBER")
private Long primaryKey;
You have to define 2 separate annotations (@GeneratedValue and
@SequenceGenerator) and hook up the @GeneratedValue.generator to your
@SequenceGenerator.name
Why couldn't it just be:
@GeneratedValue
(strategy=GenerationType.SEQUENCE,generator="S_SUBSCRIBER")
private Long primaryKey;
I want a sequence and here's the sequence name. But instead I have to
define a whole separate annotation.
Lots of copy-n-paste in all of our JPA entities.
Jacek
David
In subsequent thoughts on this issue, I've mellowed my stance
somewhat. One of the things everybody tells me lombok 'does right' is
its pragmatism: A simple video on the frontpage, a no-fuss installer,
everything 'just works', etcetera. Plugins necessarily dilute this
pragmatism. JPA *IS* very popular, and as you say, it *IS* rather
boilerplatey. I can wax rhapsodic about how JPA sucks in many regards,
and that this JPA's fault, and that we need cool new technologies that
won't be here for years, but, that's disingenuous. The same thing can
be said for something like @Cleanup, which "is java's fault", and
which cool new future technologies (java7 coin) will fix.
The only two arguments of mine that really holds water here are:
- JPA is just a library in the end. Not _everybody_ uses it, so the
standard lombok stuff should not break or become confusing for others
just to accomodate it.
- Unlike lombok's current batch of annotations, any JPA-boilerplate-
busting annotation is unlikely to be fully grokked without at least
reading up on why they do what they do.
The first problem can be solved by creating a new lombok.jpa package
that contains JPA-specific annotations. The features page can explain
specifically what all annotations in this package are for, and people
trying to write @lombok.(ctrl+space) or import lombok.(ctrl+space)
won't be bothered with them either. The actual code required to make
any JPA-specific annotation 'work' is a drop in the bucket compared to
ASM and JNA which make up ~80% of the size of lombok.jar, so that's
certainly not a good reason to leave them out. This leaves as biggest
problem my own lack of JPA experience.
The second argument can be 'solved' in two ways: First of all, Roel
has been working on an eclipse plugin that shows the delomboked code
right in your IDE, which should definitely help folks understand
what's happening behind the scenes. A proof of concept of this was
written up in an hour or two, as 'delombok' already exists, and I'm
fairly sure it won't be too difficult build such a thing for netbeans
either. Also, JPA is fundamentally complex, and a leaky abstraction is
still useful if the goal is to simplify typing and encode patterns,
and not to simplify the amount of knowledge one needs to work with it.
It does mean a lot of effort has to be put in to make the JPA
annotations as logical as we can make them so people who look at
lombok.jpa-annotated stuff aren't completely baffled.
So, what, exactly, would JPA annotations look like?
There are many concerns I do know of:
- DB sequence-powered PK fields are a pain, as jacked showed.
- Due to proxy objects, equals should use instanceof, even though
that isn't compatible with transitivity, that is the lesser evil.
- empty constructor is always needed.
- equals in general. Exactly how does this work, anyway? The equals
generated by lombok pings the fields directly and does not use
getters, but in a proxy, these fields don't actually exist. Even if
somehow a field access resulted in the JPA system magically loading
this stuff in, a single equals() or hashCode() call could cause the
JVM to chug away for hours on end as it loads every bit of data in the
database in its quest to calculate 1 hashCode, if some of the fields
are db references. The pragmatic solution to this is to define
equality on PK, but that's not technically the sense of equality that
Object.equals()' javadoc suggests. If all you want to do is use db
proxies in hashmaps, though, it's probably the right answer. Equals/
hashCode in general with JPA objects seems like a complex problem with
no right answer to me, but perhaps there's something that makes the
most sense to use as a default. Do we need a DSL-ish thing for this?
- NamedQuery could be simpler (that's what started this thread).
What else, and how do we make an annotation-based DSL that does not
require resolution to accommodate all these situations?
On Dec 21, 6:31 pm, David Goodenough <david.goodeno...@btconnect.com>
wrote:
I have a little suggestion...recently I spent some time with Python
Django and loved their dynamic ORM methods:
http://docs.djangoproject.com/en/dev/topics/db/queries/
for example:
q = Entry.objects.filter(headline__startswith="What")
maybe you could add similar functionality as well where you could add
finders with more finegrained criteria,e.g.
finders={"findByCustomerName_StartsWith","findByCustomerName_LowerCase",})
Also, in many of these finders we have custom security requirements
(e.g. some users may be able to view orders for Customer X and Y, but
others may be able to view orders only for Customer X").
It would be great if we could provide some sort of interceptor
mechanism for auto-generated finders (both pre- and post).
Pre-interceptors could be used for security access, post-interceptors
could be used for additional filtering based on security restrictions,
etc.
It could be done with AspectJ, but I'd rather do simple Java code if
possible instead.
Maybe something like:
finders={"securityCheck;findByCustomerName"})
public boolean securityCheck(String customerName) {
return allowedCustomers.contains(customerName);
}
We would also need finders by multiple fields, e.g.:
finders={"securityCheck;findByCountryCodeAndCustomerName"})
public boolean securityCheck(String countryCode, String customerName)
{
return allowedCountry.contains(countryCode);
}
etc.
Just trying to cover the use cases we have in real life apps, so that
it can be actually useful.
Great work, thank you for your efforts,
Jacek
> Petr, let me just say that this is amazing. We are more of a Spring/
> Hibernate shop, but we may decided to move to JPA (with Hibernate as
> the provider)
> just because of enhancements like these.
Hi Jacek, thanks for the kind words. See more below.
>
> I have a little suggestion...recently I spent some time with Python
> Django and loved their dynamic ORM methods:
> http://docs.djangoproject.com/en/dev/topics/db/queries/
Thanks for the link, I will investigate how this works in Django.
>
> for example:
>
> q = Entry.objects.filter(headline__startswith="What")
>
> maybe you could add similar functionality as well where you could add
> finders with more finegrained criteria,e.g.
>
> finders
> ={"findByCustomerName_StartsWith","findByCustomerName_LowerCase",})
>
It sounds that "startsWith" can be emulated with "like", or not? On
the other hand, lowerCase could be useful.
> Also, in many of these finders we have custom security requirements
> (e.g. some users may be able to view orders for Customer X and Y, but
> others may be able to view orders only for Customer X").
>
> It would be great if we could provide some sort of interceptor
> mechanism for auto-generated finders (both pre- and post).
>
> Pre-interceptors could be used for security access, post-interceptors
> could be used for additional filtering based on security restrictions,
> etc.
>
> It could be done with AspectJ, but I'd rather do simple Java code if
> possible instead.
>
> Maybe something like:
>
> finders={"securityCheck;findByCustomerName"})
>
> public boolean securityCheck(String customerName) {
> return allowedCustomers.contains(customerName);
> }
>
> We would also need finders by multiple fields, e.g.:
>
> finders={"securityCheck;findByCountryCodeAndCustomerName"})
>
> public boolean securityCheck(String countryCode, String customerName)
> {
> return allowedCountry.contains(countryCode);
> }
>
> etc.
>
I agree that having built-in support would be better than having to
use AspectJ - although for this particular case, couldn't it be done
by creating a subclass? Though that's probably not what you are
looking for. Anyway, I guess we could use the bug tracker at kenai.com
to record feature requests like this, so they are not lost in e-mail.
> Just trying to cover the use cases we have in real life apps, so that
> it can be actually useful.
That's exactly what I was looking for - thanks many times!
Petr
>
> Great work, thank you for your efforts,
> Jacek
>
>
Roel and I will talk more about where lombok should go to accomodate
@AutoFinders and other good stuff. The main issue remains how to have
lombok plugins. Perhaps Jigsaw will offer something. Clearly a
LOMBOK_HOME kind of deal is not a good idea; each project is different
and it would be classpath hell all over again if plugins are applied
to all projects indiscriminately (something that _is_ the case in
today's lombok, unfortunately). If jigsaw offers a central repository
for java modules then all lombok really needs to do is read a list of
module names per project (module) somewhere and apply them all to any
source files in that project (module). There's a minor issue of
ordering (in which order are the processors run?) but that is solvable
and probably not that important (e.g. AutoFinders and something like
Getter just don't get in each other's way, if you have both of them in
one class it doesn't matter in which order the annotations are
processed).
I can also see the benefits of lombok automatically picking up certain
(probably annotated :P) source files in a project itself, compile
them, and run them against other source files in that project. A sort
of on-the-fly macro ability for java, built around a designed-for-
public-consumption.
We're still working on the AST, which is coming along nicely so far.
> > Groups group forhttp://projectlombok.org/
> That's _fantastic_, Petr, really shows the power of DSLs. I haven't
> worked much with JPA before but this is a fairly convincing argument
> to start using it the next time I need persistence.
As an aside, I think Java Persistence is now a mature and usable
framework, and there is no reason not to use it (if O-R mapping in
general is suitable for the project). JPA 1.0 had many limitations
compared to using straight Hibernate and the Hibernate-specific APIs,
but with JPA 2.0, a lot more experience gathered by the users of
Hibernate, TopLink/EclipseLink, Kodo and other frameworks has been
folded into the standard, so JPA 2.0 does not lack compared to using
framework-specific APIs. It can also be used independently of the rest
of the Java EE 6 stack, e.g. you can just drop it into Tomcat, and
Spring 3 interoperates with JPA 2.0.
>
> Roel and I will talk more about where lombok should go to accomodate
> @AutoFinders and other good stuff. The main issue remains how to have
> lombok plugins.
One other particular obstacle I came across was the IDE support. While
the built-in Lombok annotations work well with NetBeans 6.8, to make
the @AutoFinders annotation work with NetBeans, I had to copy the
classes from the dynamic finders library and the javax.persistence.*
API classes into lombok.jar (the copy of lombok.jar that was installed
into the IDE). Hopefully, with the built-in support for annotation
processors in NetBeans 6.9, things will "just work" by adding the
libraries to your project's classpath, and no installation step will
be necessary.
> Perhaps Jigsaw will offer something. Clearly a
> LOMBOK_HOME kind of deal is not a good idea; each project is different
> and it would be classpath hell all over again if plugins are applied
> to all projects indiscriminately (something that _is_ the case in
> today's lombok, unfortunately). If jigsaw offers a central repository
> for java modules then all lombok really needs to do is read a list of
> module names per project (module) somewhere and apply them all to any
> source files in that project (module). There's a minor issue of
> ordering (in which order are the processors run?) but that is solvable
> and probably not that important (e.g. AutoFinders and something like
> Getter just don't get in each other's way, if you have both of them in
> one class it doesn't matter in which order the annotations are
> processed).
>
> I can also see the benefits of lombok automatically picking up certain
> (probably annotated :P) source files in a project itself, compile
> them, and run them against other source files in that project. A sort
> of on-the-fly macro ability for java, built around a designed-for-
> public-consumption.
I will be watching how Lombok will addess this area, and then adjust
the dynamic finders library to whatever approach Lombok takes.
>
> We're still working on the AST, which is coming along nicely so far.
>
Great to hear, I am eager to adjust the library to the new AST as
well, whenever it is ready.
Petr
> Groups group for http://projectlombok.org/