grails-data-mapping
grails-data-mapping copied to clipboard
GORM 7 Regression for Criteria projection
Given these domain classes:
class Role {
String authority
}
class User {
String username
}
class UserRole implements Serializable {
private static final long serialVersionUID = 1
User user
Role role
static mapping = {
id composite: ['user', 'role']
version false
}
}
and this query:
@ReadOnly
List<Long> findAllRoleIdsByUser(Long userId) {
def c = UserRole.createCriteria()
c.list {
projections {
role {
property('id')
}
}
user {
eq('id', userId)
}
} as List<Long>
}
With Grails 3.3.11 and GORM 6.1.12.RELEASE it works.
For Grails 4.0.2 and GORM 7.0.2, it fails with:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [role] on this ManagedType [example.grails.UserRole]
It seems to be related with the composite key of UserRole
Removing that key gets rids of the error.
Environment Information
- Operating System: MacOS
- Grails Version: 4.0.2
- JDK Version: 1.8
- Container Version (If Applicable): TODO
Example Application
https://github.com/grails-core-issues-forks/grails-core-issue-11505
Steps to reproduce with the example app
Grails 3
cd grailsthree;
./gradlew bootRun
curl localhost:8080/home
{"ids":[1,2]}
Grails 4
cd grailsfour;
./gradlew bootRun
curl localhost:8080/home
Unable to locate Attribute with the the given name [role] on this ManagedType [example.grails.UserRole]. Stacktrace follows:
...
hi, I'm facing the same issue. it seems a problem with Groovy version. I have upgraded a Grails 3 application to latest Groovy 2.5.12
and @Where
clause with an association query stopped working. It seems to me that the breaking point is around 2.5.7
which is still working. Here are the decompiled closure classes:
working one:
protected List $tt__findAllEnabled(TransactionStatus transactionStatus) {
DetachedCriteria $query = new DetachedCriteria(InstagramSearch.class);
final class __tt__findAllEnabled_closure8 extends Closure implements GeneratedClosure {
public __tt__findAllEnabled_closure8(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
final class _closure9 extends Closure implements GeneratedClosure {
public _closure9(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
final class _closure10 extends Closure implements GeneratedClosure {
public _closure10(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
final class _closure12 extends Closure implements GeneratedClosure {
public _closure12(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
return ((AbstractDetachedCriteria)((_closure12)this).getDelegate()).eq("enabled", true);
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
((AbstractDetachedCriteria)((_closure10)this).getDelegate()).and(new _closure12(this, this.getThisObject()));
final class _closure13 extends Closure implements GeneratedClosure {
public _closure13(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
return ((AbstractDetachedCriteria)((Closure)((_closure13)this).getDelegate()).getDelegate()).eq("syncEnabled", true);
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
return ScriptBytecodeAdapter.invokeMethodN(_closure10.class, ((_closure10)this).getDelegate(), (String)"account", new Object[]{new _closure13(this, this.getThisObject())});
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
((AbstractDetachedCriteria)((_closure9)this).getDelegate()).and(new _closure10(this, this.getThisObject()));
final class _closure11 extends Closure implements GeneratedClosure {
public _closure11(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
return ((AbstractDetachedCriteria)((Closure)((_closure11)this).getDelegate()).getDelegate()).eq("tokenValid", true);
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
return ScriptBytecodeAdapter.invokeMethodN(_closure9.class, ((_closure9)this).getDelegate(), (String)"account", new Object[]{new _closure11(this, this.getThisObject())});
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
return ((DetachedCriteria)((__tt__findAllEnabled_closure8)this).getDelegate()).and(new _closure9(this, this.getThisObject()));
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
DetachedCriteria var3 = $query.build(new __tt__findAllEnabled_closure8(this, this));
return (List)var3.list();
}
not working one
protected List $tt__findAllEnabled(TransactionStatus transactionStatus) {
DetachedCriteria $query = new DetachedCriteria(InstagramSearch.class);
final class __tt__findAllEnabled_closure8 extends Closure implements GeneratedClosure {
public __tt__findAllEnabled_closure8(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
final class _closure9 extends Closure implements GeneratedClosure {
public _closure9(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
final class _closure10 extends Closure implements GeneratedClosure {
public _closure10(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
final class _closure12 extends Closure implements GeneratedClosure {
public _closure12(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
return ((DetachedCriteria)((Closure)((Closure)((Closure)((_closure12)this).getOwner()).getOwner()).getOwner()).getDelegate()).eq("enabled", true);
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
((DetachedCriteria)((Closure)((Closure)((_closure10)this).getOwner()).getOwner()).getDelegate()).and(new _closure12(this, this.getThisObject()));
final class _closure13 extends Closure implements GeneratedClosure {
public _closure13(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
return ((DetachedCriteria)((Closure)((Closure)((Closure)((_closure13)this).getOwner()).getOwner()).getOwner()).getDelegate()).eq("syncEnabled", true);
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
return ScriptBytecodeAdapter.invokeMethodN(_closure10.class, ((_closure10)this).getDelegate(), (String)"account", new Object[]{new _closure13(this, this.getThisObject())});
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
((DetachedCriteria)((Closure)((_closure9)this).getOwner()).getDelegate()).and(new _closure10(this, this.getThisObject()));
final class _closure11 extends Closure implements GeneratedClosure {
public _closure11(Object _outerInstance, Object _thisObject) {
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
return ((DetachedCriteria)((Closure)((Closure)((_closure11)this).getOwner()).getOwner()).getDelegate()).eq("tokenValid", true);
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
return ScriptBytecodeAdapter.invokeMethodN(_closure9.class, ((_closure9)this).getDelegate(), (String)"account", new Object[]{new _closure11(this, this.getThisObject())});
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
return ((DetachedCriteria)((__tt__findAllEnabled_closure8)this).getDelegate()).and(new _closure9(this, this.getThisObject()));
}
public Object call(Object args) {
return this.doCall(args);
}
public Object call() {
return this.doCall((Object)null);
}
@Generated
public Object doCall() {
return this.doCall((Object)null);
}
}
DetachedCriteria var3 = $query.build(new __tt__findAllEnabled_closure8(this, this));
return (List)var3.list();
}
if you make a diff then you can see do calls in later one contains 3 times cast to Closure

I guess that the issue is linked to org.grails.datastore.gorm.query.transform.DetachedCriteriaTransformer
@puneetbehl here are some research notes.
Research Notes
I reproduced all the issues described above by sergio, in 3.3.11 and 4.0.2. In 4.0.2, I also saw how when you take the composite key our if UserRole
the query works fine.
The area to debug in 4.0.2. is AbstractHibernateCriteriaBuilder:1765
. The exception is thrown in this line final Attribute<?, ?> attribute = entityType.getAttribute(name);
Inside AbstractManagedType:114 this line: PersistentAttributeDescriptor<? super J, ?> attribute = declaredAttributes.get( name );
yields null, and then this method checkNotNull( "Attribute ", attribute, name );
throws the exception.
However if you take out the composite key in UserRole
the query works correctly. The difference is that in line :114 now the declaredAttributes
property now has role, id, and user
in it, so it yields an attribute. And the checkNotNull( "Attribute ", attribute, name );
passes through fine.
The only thing i can make of this that the AbstractManagedType
is created correctly when the composite key is NOT present and that when it IS declared, the declaredAttributes
field becomes blank so we get the exception. Of course i dont know "why".
Comparing to 3.3.11
- I tried to compare to what the code is doing in 3.3.11 grails. This turned up empty because in the code after AbstractHibernateCriteriaBuilder:1761 is not the same in 3.3.11. it has changed from grails 3.3.11 to 4.0.2. I don't see the same code in 3.3.11 where the attribute is retrieved in from the entityType.getAttribute(name);
Related issue on the hibernate5 repo: grails/gorm-hibernate5/issues/143
Just in case there's some insight there that helps with this.
I am in the process of upgrading a grails 3.11 project to 4.0.4 and am facing the same issue. When creating a criteria on the UserRole class (created by the spring security plugin), I get the "Unable to locate Attribute with the the given name [role] on this ManagedType [net.buttonwood.bcxbroker.UserRole]". My UserRole class also implements a couple of interfaces as well, so wasn't sure if that is a factor.
To add to @niravassar's research, my User Role has two additional members/attributes other than 'role' and 'user'. When debugging I can see that declaredAttributes contains the two additional attributes, but not the two that appear in the composite key definition.
Is there a known work around for this, other than to remove the composite key?
With GORM v7 (Hibernate 5.4.23), when sessionFactory is built, org.hibernate.metamodel.internal.MetadataContext.wrapUp
is called to parse all persistent classes and applyIdMetadata method is called to evaluate id properties.
In case of composite id, properties are embbeded in a org.hibernate.mapping.Component
with 2 or more elements and there is a FIXME for this case.
if ( component.getPropertySpan() > 1 ) { //FIXME we are dealing with a Hibernate embedded id (ie not type) } else { //FIXME take care of declared vs non declared property identifiableType.getInFlightAccess().applyIdAttribute( (SingularPersistentAttribute) attributeFactory.buildIdAttribute( identifiableType, (Property) component.getPropertyIterator().next() ) ); }
As an work around, using createAlias did work for me. Before
compartiment {
eq('active', true)
}
After
createAlias('compartiment', 'c')
eq('c.active', true)