Accessing fields from @MappedSuperclass in JPA

1,892 views
Skip to first unread message

timowest

unread,
Sep 27, 2011, 2:58:14 AM9/27/11
to quer...@googlegroups.com
Hi!

As a convenience, I let most entities inherit from a common @MappedSuperclass with some fields that are used in pretty much all of the entities.

Here is a simplified part of it:


@MappedSuperclass
public class CommonPersistence {

   
@Column(name = "created_on")
   
private Date createdOn;

   
@PrePersist
   
protected void onCreate() {
        createdOn
= new Date();
   
}

   
public Date getCreatedOn() {
       
return createdOn;
   
}

   
...
}



However, the createdOn field does not seem to be included in the generated query type. I looked at path initialization in the docs, but I can't figure out how to include the superclass fields.

Is it possible to include fields from a superclass in a query?

timowest

Is CommonPersistence in the same compilation unit / module like your subclasses? If not, then that's probably the reason for your problem.

In our tests the superclass fields / properties are available in the subtype querytypes.

Can you give me the fully qualified class names of your annotations? Just to be sure.

jmbe

You are correct. The superclass is in another maven module.

The annotations are all from javax.persistence, but from


<dependency>
       
<groupId>org.hibernate.java-persistence</groupId>
       
<artifactId>jpa-api</artifactId>
       
<version>2.0-cr-1</version>
</dependency>


I tried creating a new superclass and subclass for testing in the same module, and then a _super field showed up properly in the query type.


Is there any way I can access the fields from the superclass, even though it is in another module? Can I add the path myself in a query somehow?

timowest

I will test this locally a bit and provide a solution.

Fields from classes in external locations are now made available since release 1.3.4.

jmbe

Nice! Works fine when the entity inherits directly from a simple @MappedSuperclass, but there seems to be a problem with the way I use generics in the superclass.

The actual problem I'm having leads to a compilation error for unresolved fields, but I think the root cause might be the same as in the simplified example below.


@MappedSuperclass
public class CommonPersistence {

   
@Column(name = "created_on")
   
private Date createdOn;
}

@MappedSuperclass
public class CommonIdentifiablePersistence<ID extends Serializable> extends
       
CommonPersistence {

   
@Id
   
@GeneratedValue(strategy = GenerationType.AUTO)
   
@Column(updatable = false)
   
private ID id;

}

@Entity
public class Entity extends CommonIdentifiablePersistence<Long> {
}


In the example QEntity.id will have the type


   
//inherited
   
public final PSimple<java.io.Serializable> id = _super.id;


but shouldn't it be PNumber?


   
//inherited
   
public final PNumber<Long> id;


Is it possible to deduce the type of id is a Long?

timowest

Yes, this is a known issue. I will provide a fix this week.

jmbe

I've captured the compilation error I alluded to earlier in a test, if you would like to try it.


package com.mysema.query.domain;

import static org.junit.Assert.assertEquals;

import java.io.Serializable;
import java.util.Date;

import org.junit.Ignore;
import org.junit.Test;

import com.mysema.query.annotations.QueryEntity;
import com.mysema.query.annotations.QuerySupertype;
import com.mysema.query.types.path.PDateTime;
import com.mysema.query.types.path.PNumber;

/**
 * Test multiple level superclasses with generics.
 */

public class Inheritance6Test {

       
/*
         * Top superclass.
         */

       
@QuerySupertype
       
public static class CommonIdentifiable<ID extends Serializable> {
               
@SuppressWarnings("unused")
               
private ID id;

               
@SuppressWarnings("unused")
               
private Date createdOn;
       
}

       
/*
         * Intermediate superclass, equivalent to @MappedSuperclass.
         */

       
@QuerySupertype
       
public abstract static class Translation<T extends Translation<T, K>, K extends TranslationKey<T, K>>
                       
extends CommonIdentifiable<Long> {
               
@SuppressWarnings("unused")
               
private String value;
       
}

       
/*
         * Intermediate superclass, equivalent to @MappedSuperclass.
         */

       
@QuerySupertype
       
public abstract static class TranslationKey<T extends Translation<T, K>, K extends TranslationKey<T, K>>
                       
extends CommonIdentifiable<Long> {
       
}

       
@QueryEntity
       
public static class Gloss extends Translation<Gloss, GlossKey> {
       
}

       
@QueryEntity
       
public static class GlossKey extends TranslationKey<Gloss, GlossKey> {
       
}

       
@Test
       
public void gloss_subtype_should_contain_fields_from_superclass() {
                assertEquals
(String.class, QInheritance6Test_Gloss.gloss.value
                               
.getType());
       
}

       
@Test
       
public void intermediate_superclass_should_contain_fields_from_top_superclass() {
               
/*
                 * This won't compile when Translation is marked as @QuerySupertype -
                 * works fine as @QueryEntity.
                 */

               
// assertEquals(PDateTime.class,
               
// QInheritance6Test_Translation.translation.createdOn.getClass());
       
}

       
@Test
       
public void gloss_subtype_should_contain_fields_from_top_superclass() {
                assertEquals
(PDateTime.class, QInheritance6Test_Gloss.gloss.createdOn
                               
.getClass());
       
}

       
@Test
       
@Ignore
       
public void gloss_subtype_should_contain_id_from_top_superclass() {
                assertEquals
(PNumber.class, QInheritance6Test_Gloss.gloss.id.getType());
       
}

}


When Translation is marked as @QueryEntity, then the fields from the superclass gets correctly generated and the tests passes (except for the id type as you already know).

However, when Translation is marked as @QuerySupertype, then the fields from the top superclass doesn't seem to be generated in the query type. Translation is supposed to be an abstract @MappedSuperclass to I think @QuerySupertype is the equivalent annotation in the tests, right?

Never mind! The field is correctly included in the subtype, where it should be available.

Still, the other project won't compile properly - will do some more experimenting tomorrow.

timowest

The Translation issue is not a bug. Your test case should be


   
@Test
   
public void intermediate_superclass_should_contain_fields_from_top_superclass() {  
         QInheritance6Test_Translation translation
= QInheritance6Test_Gloss.gloss._super;
         assertEquals
(PDateTime.class, translation.createdOn.getClass());
   
}


For @MappedSuperclass and @QuerySupertype annotated types, the default variable is not created, since these types are not entity types, just supertypes with common mappings.

The constructors for Q-types of mapped supertypes are also different.

And I am working on the generics issues. A fix will be available soon.

jmbe

Yeah, I tripped myself up when switching back and forth between @QueryEntity and @QuerySupertype when testing. Sorry about that. smile

timowest

No problem smile

The generics issues have been fixed in release 1.3.11. The subtype properties have now types that reflect the generic parameter bindings of the subtype.

Sorry that it took some time.

jmbe

Yes, I can confirm that the generic type is generated correctly now!

I'll have another go at reproducing the compilation problem I've been experiencing since after 1.3.3 (last version that works). It definitely seems to be caused by inheriting from a supertype in another module.

Sorry for posting this much code. smile But for the java classes at least, you can just paste the text into the correct package and Eclipse will create proper source files.


Add to QuerydslAnnotationProcessorTest (this test passes):


   
@Test
   
public void processGenericInheritanceWithSuperclassInExternalModule() throws IOException {
        process
(Collections.singletonList(packagePath + "Inheritance8Test.java"));
   
}


New test in querydsl-apt (second test fails):


package com.mysema.query.domain;

import static org.junit.Assert.assertEquals;

import java.io.Serializable;

import org.junit.Test;

import com.mysema.query.annotations.QueryEntity;
import com.mysema.query.annotations.QuerySupertype;
import com.mysema.query.test.domain.CommonPersistence;
import com.mysema.query.types.path.PNumber;

public class Inheritance8Test {

   
@QuerySupertype
   
public static class CommonIdentifiable<ID extends Serializable> extends CommonPersistence {
       
@SuppressWarnings("unused")
       
private ID id;
   
}

   
@QueryEntity
   
public static class SimpleSubclass extends CommonPersistence {
   
}

   
@QueryEntity
   
public static class GenericSubclass extends CommonIdentifiable<Long> {
   
}

   
@Test
   
public void simple_subclass_should_contain_fields_from_external_superclass() {
        assertEquals
(PNumber.class, QInheritance8Test_SimpleSubclass.simpleSubclass.version.getClass());
   
}

   
@Test
   
public void generic_subclass_should_contain_fields_from_external_superclass() {
        assertEquals
(PNumber.class, QInheritance8Test_GenericSubclass.genericSubclass.version.getClass());
   
}

}



New test-jar module querydsl-test with this pom (note, I haven't bothered to add versions to <dependencyManagement> in the root pom, so there is some duplication here):


<?xml version="1.0" encoding="UTF-8"?>
<project
   
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
   
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   
<modelVersion>4.0.0</modelVersion>

   
<parent>
       
<groupId>com.mysema.querydsl</groupId>
       
<artifactId>querydsl-root</artifactId>
       
<version>1.3.11-SNAPSHOT</version>
   
</parent>

   
<groupId>com.mysema.querydsl</groupId>
   
<artifactId>querydsl-test</artifactId>
   
<name>Querydsl - Test support</name>
   
<packaging>jar</packaging>

   
<properties>
       
<jdo.version>2.3-eb</jdo.version>
   
</properties>

   
<dependencies>
       
<dependency>
           
<groupId>commons-io</groupId>
           
<artifactId>commons-io</artifactId>
           
<version>1.4</version>
           
<!-- license : Apache License 2.0 -->
       
</dependency>
       
<dependency>
           
<groupId>com.mysema.querydsl</groupId>
           
<artifactId>querydsl-core</artifactId>
           
<version>${project.parent.version}</version>
       
</dependency>

       
<!-- test -->
       
<dependency>
           
<groupId>joda-time</groupId>
           
<artifactId>joda-time</artifactId>
           
<version>1.6</version>
           
<scope>test</scope>
       
</dependency>
       
<dependency>
           
<groupId>javax.jdo</groupId>
           
<artifactId>jdo2-api</artifactId>
           
<version>${jdo.version}</version>
           
<scope>test</scope>
       
</dependency>
       
<dependency>
           
<groupId>org.hibernate</groupId>
           
<artifactId>hibernate-annotations</artifactId>
           
<version>3.4.0.GA</version>
           
<scope>test</scope>
       
</dependency>
   
</dependencies>

   
<build>
       
<plugins>
           
<plugin>
               
<groupId>org.apache.maven.plugins</groupId>
               
<artifactId>maven-jar-plugin</artifactId>
               
<version>2.2</version>
               
<executions>
                   
<execution>
                       
<goals>
                           
<goal>test-jar</goal>
                       
</goals>
                   
</execution>
               
</executions>
           
</plugin>

           
<plugin>
               
<groupId>com.mysema.maven</groupId>
               
<artifactId>maven-apt-plugin</artifactId>
               
<version>0.3.1</version>
               
<configuration>
                   
<outputDirectory>target/generated-test-sources/java</outputDirectory>
               
</configuration>
               
<executions>
                   
<execution>
                       
<id>jpa</id>
                       
<phase>generate-test-sources</phase>
                       
<goals>
                           
<goal>test-process</goal>
                       
</goals>
                       
<configuration>
                           
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
                       
</configuration>
                   
</execution>
                   
<execution>
                       
<id>jdo</id>
                       
<phase>generate-test-sources</phase>
                       
<goals>
                           
<goal>test-process</goal>
                       
</goals>
                       
<configuration>
                           
<processor>com.mysema.query.apt.jdo.JDOAnnotationProcessor</processor>
                       
</configuration>
                   
</execution>
                   
<execution>
                       
<id>qd</id>
                       
<phase>generate-test-sources</phase>
                       
<goals>
                           
<goal>test-process</goal>
                       
</goals>
                       
<configuration>
                           
<processor>com.mysema.query.apt.QuerydslAnnotationProcessor</processor>
                       
</configuration>
                   
</execution>
               
</executions>
           
</plugin>
           
<plugin>
               
<groupId>org.codehaus.mojo</groupId>
               
<artifactId>build-helper-maven-plugin</artifactId>
               
<version>1.3</version>
               
<executions>
                   
<execution>
                       
<phase>generate-sources</phase>
                       
<goals>
                           
<goal>add-test-source</goal>
                       
</goals>
                       
<configuration>
                           
<sources>
                               
<source>target/generated-test-sources/java</source>
                           
</sources>
                       
</configuration>
                   
</execution>
               
</executions>
           
</plugin>
       
</plugins>
   
</build>

</project>


One test class in querydsl-test/src/test/java:


package com.mysema.query.test.domain;

import javax.persistence.MappedSuperclass;
import javax.persistence.Version;

@MappedSuperclass
public class CommonPersistence {
   
@SuppressWarnings("unused")
   
@Version
   
private Long version;
}


And finally, the new dependency in querydsl-apt/pom.xml


   
<dependency>
       
<groupId>com.mysema.querydsl</groupId>
       
<artifactId>querydsl-test</artifactId>
       
<version>${project.parent.version}</version>
       
<scope>test</scope>
       
<type>test-jar</type>
   
</dependency>  



The only test that fails is the last one in Inheritance8Test, but the generated Q-query type contains a compilation error for an unresolved field. Also, it looks like the class from the external module, QCommonPersistence, is generated both in its containing module and then again in querydsl-apt. That might be by design though?

Oops, disregard the last sentence about QCommonPersistence showing up twice! I just hadn't cleaned up properly from an earlier test.

timowest

The issue with external supertypes has been fixed now. The release with the fixes is 1.3.12.

You mixed QuerySupertype and MappedSuperclass in your test case, but otherwise it described your problem quite well.

Thanks again for the bug report.

jmbe

The module where I first encountered the compilation problem works flawlessly now. Thanks!

Still, there is one module that won't compile. On the surface, it is exactly the same setup as the other one (it is just another module in the same project).

I have this


@Entity
public class Account extends CommonIdentifiablePersistence<Long> {
}


but I get an NPE on type.getCategory() on line 98 in TypeMappings when getting the query type for CommonIdentifiablePersistence (which is in another module)


java
.lang.NullPointerException
        at com
.mysema.query.codegen.TypeMappings.getQueryType(TypeMappings.java:98)
        at com
.mysema.query.codegen.TypeMappings.getPathType(TypeMappings.java:94)
        at com
.mysema.query.codegen.TypeMappings.getPathType(TypeMappings.java:90)
        at com
.mysema.query.codegen.EntitySerializer.introSuper(EntitySerializer.java:342)
        at com
.mysema.query.codegen.EntitySerializer.intro(EntitySerializer.java:210)
        at com
.mysema.query.codegen.EntitySerializer.serialize(EntitySerializer.java:409)
        at com
.mysema.query.apt.Processor.serialize(Processor.java:355)
        at com
.mysema.query.apt.Processor.process(Processor.java:151)
        at com
.mysema.query.apt.jpa.JPAAnnotationProcessor.process(JPAAnnotationProcessor.java:45)
        at com
.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.j
ava
:624)
        at com
.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnviron
ment
.java:553)
        at com
.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.ja
va
:698)
        at com
.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:981)
        at com
.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:727)
        at com
.sun.tools.javac.main.Main.compile(Main.java:353)
        at com
.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:115)
        at com
.mysema.maven.apt.AbstractProcessorMojo.execute(AbstractProcessorMojo.java:148)
        at org
.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:490)


QAccount gets properly generated (and it gets generated before the supertype - does the order matter?), but QCommonIdentifiablePersistence gets cut in half:


package se.bliss.persistence;

import com.mysema.query.types.path.*;
import static com.mysema.query.types.path.PathMetadataFactory.*;

/**
 * QCommonIdentifiablePersistence is a Querydsl query type for CommonIdentifiablePersistence
 */

public class QCommonIdentifiablePersistence extends PEntity<CommonIdentifiablePersistence<? extends java.io.Serializable>> {

   
private static final long serialVersionUID = 1999697412;

   
public static final QCommonIdentifiablePersistence commonIdentifiablePersistence = new QCommonIdentifiablePersistence("commonIdentifiablePersistence");




I have verified using mvn dependency:tree that I am using version 1.3.12 of querydsl.

I have so far been unable to produce a failing test for this, so the problem might just as well be in my own setup. I think I'll be able to set aside some time for this either during the weekend or next week, so that I can provide you with a proper test case.

By the way, where is the query type for a superclass supposed to be generated when referring to a superclass from another module?

Everything seems fine if I just remove the incorrect file because it will then use (the correctly generated) QCommonIdentifiablePersistence from the module where CommonIdentifiablePersistence lives.

Would there be any problems with simply skipping generating query types for external classes? I guess one problem is knowing whether a query type actually exists in the external module or not, so that is why you are always generating one.

timowest

The NPE issues have been fixed in 1.3.13


> By the way, where is the query type for a superclass supposed to be generated when referring to a superclass from another module?


The newest release generates the Q-types only for cases, that are not yet available in the classpath.

Thanks very much for the test cases. They are a good help for making Querydsl more robust.

jmbe

Confirmed fixed!

I can now use Querydsl in all modules in all of my main projects. biggrin

Thank you very much, and have a nice weekend!

timowest

Great! smile

Have a nice weekend too.
Reply all
Reply to author
Forward
0 new messages