20

I have module A and module B which both have JPA annotated classes. Module B has an unit test that pulls in a couple of those entities from A. Both modules compile fine, the runtime dependencies are set OK, but I get the following error when I try to run the unit test:

java.lang.IllegalArgumentException: Unknown entity: MyClassHere
Caused by: org.hibernate.MappingException: Unknown entity: MyClassHere

This occurs in the EntityManager.merge call.

Since module B has all the hibernate config files etc, I'm guessing it's simply not picking up that my class from A is an entity.

I tried adding the following to persistence.xml

<exclude-unlisted-classes>false</exclude-unlisted-classes>

In hibernate.cfg.xml I added:

<property name="packagesToScan">myNamespace.*</property>

Then:

 <property name="packagesToScan">
                <array>
                    <value>myNamespace.*</value>
                </array>
</property>

That gave me an error that content of "property" must match null. Then I tried:

<mapping class="myNamespace.*" />

What am I missing?

Edit: One thing that I forgot to mention that might be of significance is that the two modules are set up as separate projects (I'm using eclipse) so the directory structure is different. The run time dependencies are all set up correctly but since the .class files end up in different directories, I think hibernate might not be scanning those.

ventsyv
  • 3,316
  • 3
  • 27
  • 49
  • 2
    Are you using Spring? I have a similar set up, which is working using the [setPackagesToScan](http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.html#setPackagesToScan-java.lang.String...-) override provided in [LocalContainerEntityManagerFactoryBean](http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.html). – Steve Chambers Oct 26 '15 at 14:04
  • 2
    Can you try with `myNamespace` (i.e. without the `.*`): this property should point to a parent package of the classes to scan. E.g. if the class `MyEntity` is in package `my.package.MyEntity`, you would write `my.package` – Tunaki Oct 26 '15 at 14:14
  • Tried it, it did not work... – ventsyv Oct 26 '15 at 14:52

7 Answers7

4

If you configure your project to autodetect the entities, it will only scan the path where the META-INF/persistence.xml is (by default).

In addition to :

<exclude-unlisted-classes>false</exclude-unlisted-classes>

You set an extra hibernate option :

<property name="hibernate.archive.autodetection" value="class, hbm" />

It determines which element is auto discovered by Hibernate Entity Manager.

For extra entities (in others jars), you can set the jar-file section in your main persistence.xml file :

<persistence>
    <persistence-unit name="myUnit">
        ...
        <class>foo.bar.Entity1</class>
        <class>foo.bar.Entity2</class>
        <jar-file>moduleB.jar</jar-file>
        ...
    </persistence-unit>
</persistence>

The jar-file element specifies JAR files that are visible to the packaged persistence unit that contain managed persistence classes, whereas the class element explicitly names managed persistence classes.

The JAR file or directory whose META-INF directory contains persistence.xml is called the root of the persistence unit. The scope of the persistence unit is determined by the persistence unit’s root. Each persistence unit must be identified with a name that is unique to the persistence unit’s scope.

Regards, André

André Blaszczyk
  • 750
  • 4
  • 17
  • 1
    According to the documentation, class, hbm is the default value for this setting. I tried it anyway and nothing changed. – ventsyv Oct 26 '15 at 13:09
  • I have my entities spread around multiple modules (using xml configuration) and kept getting the "Not a managed type" exception, adding my modules jar file like noted in this answer solved this problem. Without this line jpa could only find the entities in the jar that had the persistence.xml . – Tinus Tate Dec 10 '18 at 16:43
2
  • If you are using hibernate/spring we can extend the LocalSessionFactoryBean object and scan through the project to identify entity classes in the project.
  • Since you are saying two different projects, then try to write some build time utility to parse the two projects and created one entity xml file that solve your problem.
BValluri
  • 916
  • 1
  • 6
  • 10
1

The simple way to do that

configuration.addAnnotatedClass(Contact.class)

if you would like to use scan by package, first load all classes using ClassLoader. See the example source code from Hibernate-orm LocalSessionFactoryBuilder.class

@Bean
public SessionFactory sessionFactory(){

    HibernateConfig configuration = new HibernateConfig();

    Properties properties = hibernateProperties();

    configuration.setProperties(properties);

    configuration.scanPackages("com.atcc.stom.model.entity");

    return configuration.buildSessionFactory();
}

HibernateConfig.class

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.cfg.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;

import javax.persistence.AttributeConverter;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Set;
import java.util.TreeSet;

public class HibernateConfig extends Configuration {

    private static final TypeFilter[] DEFAULT_ENTITY_TYPE_FILTERS = new TypeFilter[] {
            new AnnotationTypeFilter(Entity.class, false),
            new AnnotationTypeFilter(Embeddable.class, false),
            new AnnotationTypeFilter(MappedSuperclass.class, false)};

    private static final String RESOURCE_PATTERN = "/**/*.class";

    private static final String PACKAGE_INFO_SUFFIX = ".package-info";

    private final ResourcePatternResolver resourcePatternResolver;

    private static TypeFilter converterTypeFilter;

    static {
        try {
            @SuppressWarnings("unchecked")
            Class<? extends Annotation> converterAnnotation = (Class<? extends Annotation>)
                    ClassUtils.forName("javax.persistence.Converter", Configuration.class.getClassLoader());
            converterTypeFilter = new AnnotationTypeFilter(converterAnnotation, false);
        }
        catch (ClassNotFoundException ex) {
            // JPA 2.1 API not available - Hibernate <4.3
        }
    }

    public HibernateConfig() {
        this(new PathMatchingResourcePatternResolver());
    }

    public HibernateConfig(ClassLoader classLoader) {
        this(new PathMatchingResourcePatternResolver(classLoader));
    }

    public HibernateConfig(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
    }

    public void scanPackages(String... packagesToScan) throws HibernateException {
        Set<String> entityClassNames = new TreeSet<String>();
        Set<String> converterClassNames = new TreeSet<String>();
        Set<String> packageNames = new TreeSet<String>();
        try {
            for (String pkg : packagesToScan) {
                String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                        ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;

                Resource[] resources = this.resourcePatternResolver.getResources(pattern);
                MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
                for (Resource resource : resources) {
                    if (resource.isReadable()) {
                        MetadataReader reader = readerFactory.getMetadataReader(resource);
                        String className = reader.getClassMetadata().getClassName();
                        if (matchesEntityTypeFilter(reader, readerFactory)) {
                            entityClassNames.add(className);
                        }
                        else if (converterTypeFilter != null && converterTypeFilter.match(reader, readerFactory)) {
                            converterClassNames.add(className);
                        }
                        else if (className.endsWith(PACKAGE_INFO_SUFFIX)) {
                            packageNames.add(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length()));
                        }
                    }
                }
            }
        }
        catch (IOException ex) {
            throw new MappingException("Failed to scan classpath for unlisted classes", ex);
        }
        try {
            ClassLoader cl = this.resourcePatternResolver.getClassLoader();
            for (String className : entityClassNames) {
                addAnnotatedClass(cl.loadClass(className));
            }
            for (String className : converterClassNames) {
                ConverterRegistrationDelegate.registerConverter(this, cl.loadClass(className));
            }
            for (String packageName : packageNames) {
                addPackage(packageName);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new MappingException("Failed to load annotated classes from classpath", ex);
        }
    }

    private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
        for (TypeFilter filter : DEFAULT_ENTITY_TYPE_FILTERS) {
            if (filter.match(reader, readerFactory)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Inner class to avoid hard dependency on JPA 2.1 / Hibernate 4.3.
     */
    private static class ConverterRegistrationDelegate {

        @SuppressWarnings("unchecked")
        public static void registerConverter(Configuration config, Class<?> converterClass) {
            config.addAttributeConverter((Class<? extends AttributeConverter<?, ?>>) converterClass);
        }
    }

}
Vahe Gharibyan
  • 5,277
  • 4
  • 36
  • 47
  • For above, what version of Hibernate is being used here? Also I don't see implementation for this method: Properties properties = hibernateProperties(); – BodhiOne Feb 14 '22 at 16:04
0

persistence.xml may contain <jar-file>....jar</jar-file> element: specifies one or more JAR files that will be searched for classes.

sibnick
  • 3,995
  • 20
  • 20
0

We solved a similar problem on the project I'm working on by using Spring to detect the entities. E.g. using an annotated Spring configuration:

@Configuration
@ComponentScan("com.org.prj.myNamespace1", "com.org.prj.myNamespace2")
public class MyDatabaseConfig {
    @Bean
    public EntityManagerFactory entityManagerFactory() {
        final LocalContainerEntityManagerFactoryBean factory =
            new LocalContainerEntityManagerFactoryBean();

        // ...JPA properties, vendor adaptor, dialect, data source, persistence unit etc...

        factory.setPackagesToScan("com.org.prj.myNamespace1", "com.org.prj.myNamespace2");

        factory.afterPropertiesSet();
        return factory.getObject();
    }

    // ...Data source beans etc...
}
Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
0

I recently solved a similar problem, adding the jar path to the file persistence.xml

<jar-file>file:///C:/yourpath/yourJar.jar</jar-file>

Hope it helps.  

VLS
  • 2,306
  • 4
  • 22
  • 17
-1
  • src/main/resources: applicationContext.xml
  • src/test/resources: test-applicationContext.xml

Make sure in the test-scope you also create your application-context to scan for those entities. Your test-applicationContext.xml might not set-up the entire application-context as at runtime, but some things which are also needed at test-time should also there be included, like your package-scanning.

You can ofcourse create a persistence.xml in src/main/resources and include it in both the applicationContext.xml and test-applicationContext.xml

Fico
  • 619
  • 8
  • 12