如何配置休眠以扫描不同模块中的实体

Posted

技术标签:

【中文标题】如何配置休眠以扫描不同模块中的实体【英文标题】:How to configure hibernate to scan for entities in a different module 【发布时间】:2016-01-23 04:41:45 【问题描述】:

我有模块 A 和模块 B,它们都有 JPA 注释类。模块 B 有一个单元测试,它从 A 中提取了几个这样的实体。 两个模块都编译正常,运行时依赖项设置正常,但是当我尝试运行单元测试时出现以下错误:

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

这发生在 EntityManager.merge 调用中。

由于模块 B 具有所有休眠配置文件等,我猜它根本没有从 A 中提取我的类是一个实体。

我尝试将以下内容添加到 persistence.xml

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

在 hibernate.cfg.xml 我添加了:

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

然后:

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

这给了我一个错误,即“属性”的内容必须匹配空值。 然后我尝试了:

<mapping class="myNamespace.*" />

我错过了什么?

编辑:我忘了提到可能很重要的一件事是这两个模块设置为单独的项目(我使用的是 eclipse),因此目录结构不同。运行时依赖项都已正确设置,但由于 .class 文件最终位于不同的目录中,我认为 hibernate 可能不会扫描那些。

【问题讨论】:

你在使用 Spring 吗?我有一个类似的设置,它正在使用LocalContainerEntityManagerFactoryBean 中提供的setPackagesToScan 覆盖。 你能用&lt;property name="packagesToScan"&gt;myNamespace&lt;/property&gt;试试吗(即没有.*):这个属性应该指向要扫描的类的父包。例如。如果MyEntity 类在包my.package.MyEntity 中,你会写&lt;property name="packagesToScan"&gt;my.package&lt;/property&gt; 试过了,还是不行…… 【参考方案1】:

如果您将项目配置为自动检测实体,它只会扫描 META-INF/persistence.xml 所在的路径(默认情况下)。

除了:

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

你设置了一个额外的休眠选项:

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

它确定 Hibernate Entity Manager 自动发现的元素。

对于额外的实体(在其他 jar 中),您可以在主 persistence.xml 文件中设置 jar-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>

jar-file 元素指定对包含托管持久性类的打包持久性单元可见的 JAR 文件,而 class 元素显式命名托管持久性类。

META-INF 目录包含persistence.xml 的JAR 文件或目录称为持久单元的根。持久化单元的范围由持久化单元的根决定。每个持久性单元都必须用一个对持久性单元的范围唯一的名称来标识。

问候,安德烈

【讨论】:

根据文档,class,hbm是这个设置的默认值。无论如何我都试过了,没有任何改变。 我的实体分布在多个模块中(使用 xml 配置)并不断收到“非托管类型”异常,添加我的模块 jar 文件(如本答案中所述)解决了这个问题。如果没有这一行,jpa 只能在 jar 中找到具有 persistence.xml 的实体。【参考方案2】: 如果您使用的是 hibernate/spring,我们可以扩展 LocalSessionFactoryBean 对象并通过项目扫描到 识别项目中的实体类。 既然你说的是两个不同的项目,那就试着写 一些构建时间实用程序来解析两个项目并创建一个 解决您的问题的实体 xml 文件。

【讨论】:

【参考方案3】:

简单的方法

configuration.addAnnotatedClass(Contact.class)

如果您想使用按包扫描,首先使用ClassLoader 加载所有类。查看来自Hibernate-ormLocalSessionFactoryBuilder.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.clas-s-reading.CachingMetadataReaderFactory;
import org.springframework.core.type.clas-s-reading.MetadataReader;
import org.springframework.core.type.clas-s-reading.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);
        
    


【讨论】:

【参考方案4】:

persistence.xml 可能包含&lt;jar-file&gt;....jar&lt;/jar-file&gt; 元素:指定一个或多个JAR 文件,用于搜索类。

【讨论】:

【参考方案5】:

我们通过使用Spring 检测实体解决了我正在处理的项目中的类似问题。例如。使用带注释的 Spring 配置:

@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...

【讨论】:

【参考方案6】:

我最近解决了一个类似的问题,将jar路径添加到文件persistence.xml中

&lt;jar-file&gt;file:///C:/yourpath/yourJar.jar&lt;/jar-file&gt;

希望对您有所帮助。

【讨论】:

【参考方案7】: src/main/resources: applicationContext.xml src/test/resources: test-applicationContext.xml

确保在测试范围内您还创建了应用程序上下文以扫描这些实体。您的 test-applicationContext.xml 可能不会像在运行时那样设置整个应用程序上下文,但也应该包括一些在测试时也需要的东西,例如您的包扫描。

您当然可以在 src/main/resources 中创建一个 persistence.xml 并将其包含在 applicationContext.xml 和 test-applicationContext.xml 中

【讨论】:

以上是关于如何配置休眠以扫描不同模块中的实体的主要内容,如果未能解决你的问题,请参考以下文章

休眠搜索以处理两个不同实体对象的相同索引

如何配置休眠 jpa 以使用 mysql ndb(集群)

如何配置 Spring Boot 以使用两个数据库?

如何配置 PHP 以使用 `clamscan` 自动扫描上传的文件?

使用多个数据库休眠

在休眠中为同一实体的 MySQL 和 SQL 服务器配置差异表名称