Reduce boilerplate code for long paths

Bug #500163 reported by Anatoly Kupriyanov
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
Querydsl
Fix Released
Wishlist
Timo Westkämper

Bug Description

Now if I have entity with links to other entities I cannot use long path easily.
E.g.
QEntity.entity.link1.link2
It produces null pointer exception, because path is not initialized. One way is to use @QueryInit, but it is not very neat solution - it pollutes entity class and requires "string" name.
Other solution is something like
QLink1 l1 = new QLink1(QEntity.entity.link1.getMetadata(), PathInits.DIRECT);
QLink2 l2 = new QLink2(l1.link2.getMetadata(), PathInits.DIRECT);
It works but it's very verbose.
Is it possible to do something more friendly. I could suggest several variants:
1. generate yet another constructor new QLink1(QEntity.entity.link1)
2. generate a special function QEntity.entity.link1.init().link2.init()

I prefer the second variant.

Revision history for this message
Timo Westkämper (timo-westkamper) wrote :

> @QueryInit, but it is not very neat solution - it pollutes entity class and requires "string" name.

It is true, that QueryInit is not type-safe, but we didn't come up with a better solution.

> Is it possible to do something more friendly. I could suggest several variants:
> 1. generate yet another constructor new QLink1(QEntity.entity.link1)

Could you explain this more?

> 2. generate a special function QEntity.entity.link1.init().link2.init()

We had something like this before, but dropped it, because we favored the declarative approach with QueryInit. Providing manual initializations has the disadvantage, that the property paths cannot be declared final. We keep the paths final, so manual initialization is not an option.

Another alternative would be to provide QueryInit style initializations, but declare them elsewhere. For example in a package

@Initializations({
  @Initialization(type=Entity.class, path="prop1.prop2")
  @Initialization(type=Entity2.class, path="prop3")
})

What do you think?

Revision history for this message
Anatoly Kupriyanov (kan-izh) wrote :

1. now I do "new QLink1(QEntity.entity.link1.getMetadata(), PathInits.DIRECT)"
Just to make code generation for a constructor:
public QLink1(QLink1 src)
{
  super(src.getMetadata(), PathInits.DIRECT);
}

2. init() could create a new object, not modify the current one.

I don't like string paths like "prop1.prop2" - because it's raw text...

Revision history for this message
Timo Westkämper (timo-westkamper) wrote :

For the moment using QueryInit is the way to go. It is a declarative approach and enables path initializations for types instead of type instances, which is an advantage in our opinion.

As for your example, it can be expressived with QueryInit without a reference to the property :

QEntity.entity.link1.link2

Entity {
   @QueryInit("*")
   private QLink1 link1;
}

Declarating the initializations directly in the property context instead of package context reduces the amount of String based references, as the declaring type and annoted property define already some context.

Btw, every annotation can be considered a "pollution" of the host class. Querydsl annotations are in no way inferior annotations compared to JPA or Hibernate annotations.

If using QueryInit is for some reason no option to you can fall back to using HQL inner joins, they are supported in Querydsl and reduce issues with long paths.

Merry christmas!

Changed in querydsl:
importance: Undecided → Wishlist
assignee: nobody → Timo Westkämper (timo-westkamper)
Revision history for this message
Anatoly Kupriyanov (kan-izh) wrote :

This is small patch which does what I want. It adds a function "dir()" which creates a new Q-object with initialised direct paths.
So now it's possible to use it like this:
QEntity.entity.link1.dir().link2.dir().link3.id.eq(42)
And all types are checked at compile-time.

There is more neat solution - to change public fields to methods, so link1 will be not a field, but a methods, which creates a new Q-object with initialized direct paths.
QEntity.entity.link1().link2().link3().id.eq(42)
But it's not easy to change now, not breaking an existing code. Or as alternative create both - and methods, and fields...

Join aliases which you mentioned is not very neat solution - first of all it requires to declare a local variable, and moreover - it doesn't work for subqueries.

BTW, is there any explanation why sub-queries don't support joins?

Merry Christmas! :)

Revision history for this message
Timo Westkämper (timo-westkamper) wrote :

> There is more neat solution - to change public fields to methods, so link1 will be not a field, but a methods, which creates a new Q-object with initialized direct paths.

Ok, maybe the method based path initialization could be offered as an alternative to QueryInit style initialization, and it could be activated via some configuration parameter.

I will implement it next week.

> BTW, is there any explanation why sub-queries don't support joins?

I didn't find any HQL examples with subqueries using joins. Are those supported in HQL?

Revision history for this message
Timo Westkämper (timo-westkamper) wrote :

Fixed in SVN trunk :

Use the QuerydslConfig annotation on package or type level to tune the serialization :

public class QuerydslConfigTest {

    @QuerydslConfig(entityAccessors=true, listAccessors = true, mapAccessors= true)
    @QueryEntity
    public static class Entity{

        Entity prop1;

        Entity prop2;

        List<Entity> entityList;

        Map<String,Entity> entityMap;
    }

    @Test
    public void test(){
        assertEquals("entity.prop1.prop2.prop1", QQuerydslConfigTest_Entity.entity.prop1().prop2().prop1().toString());
    }

}

Changed in querydsl:
status: New → Fix Committed
Revision history for this message
Timo Westkämper (timo-westkamper) wrote :

Btw, feel free to suggest other fine tunings of the serialization.

Revision history for this message
Timo Westkämper (timo-westkamper) wrote :

Fixed in release 1.0.2

Changed in querydsl:
status: Fix Committed → Fix Released
Revision history for this message
Cyberax (alex-besogonov) wrote :

BTW, a way to completely disable generation of public non-final fields would be nice. It's extremely easy to catch NullPointerException by accessing them when they are not initialized.

Revision history for this message
Timo Westkämper (timo-westkamper) wrote :

Ok, maybe the entityAccessors flag could set the visibility of the entity fields to protected.

Anatoly and Cyberax, what do you think? Does this approach fit your Querydsl usage style?

We had accessors for entity fields in earlier version of Querydsl, but removed them, because we wanted a consistent way to access properties in Querydsl types.

QueryInit is a good tool to avoid NPE:s, but entity accessors can be activated as well. Protected entity fields would be consistent with this approach, IMHO. Literal fields will stay fields, they are eagerly initialized anyway.

Revision history for this message
Cyberax (alex-besogonov) wrote :

Yes, protected fields should work just fine. It'll be impossible to hit them accidentally, and they play well with inheritance.

I agree that literal fields should just stay fields. As a nice side-effect, my IDE highlights them differently.

Revision history for this message
Timo Westkämper (timo-westkamper) wrote :

Further improvements released in 1.0.3

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.