Eeko Eeko - 5 months ago 77
Java Question

Selecting nested Hibernate immutable objects throws org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: new

Using:


  • Hibernate 4.1.6

  • Spring 4.1

  • Java 8



I have two immutable data models:

Parent model

@Entity
@Immutable
@Table(name="PARENT_TABLE")
public class Parent {
@Column(name="NAME")
private final String name;

@OneToMany(cascade=CascadeType.ALL, mappedBy="parent")
@MapKey(name="key")
Map<String, Child> children = new HashMap<>(1);

public Parent(String name) {
this.name = name;
}

public putChild(Child c) {
Child childWithRef = new Child(this, c.getKey());
children.put(c.getKey(), childWithRef);
}
}


Child model

@Entity
@Immutable
@Table(name="CHILD_TABLE")
public class Child {
@ManyToOne
@JoinColumn(name="PARENT_ID") //say Parent has a generated ID
private final Parent parent;

@Column(name="KEY")
private final String key;

public Child(Parent p, String key) {
this.parent = p;
this.key = key;
}
}


And I want to fetch all the children, with their parents, so I try running (
entityManager
is from Spring):

entityManager.createQuery("SELECT new Child(new Parent('sample name'), c.key) FROM Child c").getResultList()


But I get an exception (the problem indicated is with the second
new
):

org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: new near line 1, column ...


Does anyone know how to fetch nested immutable objects?

I know Hibernate is really against immutability. Since I'm only performing simple database tasks, I'd like to try nonetheless.

Answer

Your query is illegal for JPQL. The JPQL BNF (JPA 2.1 spec) states very clearly

select_expression ::= single_valued_path_expression | scalar_expression | aggregate_expression | 
    identification_variable | OBJECT(identification_variable) | constructor_expression
constructor_expression ::= NEW constructor_name ( constructor_item {, constructor_item}* )
constructor_item ::= single_valued_path_expression | scalar_expression | aggregate_expression |
    identification_variable

You cannot nest constructor expressions.

Extract it out into a single object and then split into your structure in your code. For example

SELECT c.key FROM Child c

to get the "key" of the child, and then create the objects yourself in your code.

EDIT FOR FULL CODE:

Here is the full solution in case someone needs it too (there are probably many ways to improve and add performance, so use this as base for understanding):

//fetch all fields without instantiating any models from Hibernate;
//all aliases are for understanding only;
//notice `INNER JOIN c.parent` for fetching the parent data
List<Object[]> results = entityManager.createQuery("SELECT p.id as parentId, p.name as parentName, p.key as parentKey, c.key as childKey FROM Child c INNER JOIN c.parent").getResultList()

//use map for parents identification
Map<Long, Parent> parentsMap = new HashMap<>(results.size())

//now use the data to instantiate the immutable models
results.forEach(resultRecord -> {
    Long parentId = (Long) resultRecord[0];
    if (parentsMap.get(parentId) == null) { //first time meeting this parent
        Parent p = new Parent(resultRecord[0], resultRecord[1], resultRecord[2]);
        parentsMap.put(parentId, p);
    }

    Child c = new Child(parentsMap.get(parentId), resultRecord[3]);
    parentsMap.get(parentId).putChild(c);
}