具有运行时 pojos 的带有 Hibernate 的 OSGi 片段包

Posted

技术标签:

【中文标题】具有运行时 pojos 的带有 Hibernate 的 OSGi 片段包【英文标题】:OSGi Fragment bundle with Hibernate having Runtime pojos 【发布时间】:2012-09-10 09:50:08 【问题描述】:

我的要求是使用 hibernate 映射各种数据库(特别是 SQL Server、mysql 和 Postgres);从 db 记录创建一个 xml 文件。

对于休眠,我正在使用 JAssist 在运行时创建 hbm 文件和 pojos。 我的代码运行良好,为了进一步模块化,我为每个数据库实现了片段包。这样我的主机包将处理运行时类的创建并将它们添加到类加载器、hbm 文件创建逻辑和 BL 中。片段通过传递参数调用它。

当我为每个数据库创建一个片段包时, 在我的主机包中创建的运行时 pojo 类在我的片段包中可见, 我检查了“Thread.currentThread().getContextClassLoader().loadClass()” 并能够创建它的实例,

问题是 当我从片段包调用 Hibernate 函数时,我得到“实体未映射”,AFAIK 当休眠无法找到带有表的映射类时出现这些异常。 所以我猜 Hibernate 没有找到我的运行时 pojo 类。它可以在主机中找到。

主机: 运行时 Pojo 创建, HBM 和 CFG 创建和更新逻辑 BL

片段: 休眠层, 调用休眠函数, XML 创建逻辑

【问题讨论】:

【参考方案1】:

Hibernate OSGi 目前有几个注意事项,其中一个需要单个持久性单元客户端捆绑包。由于各种原因,我们在构建负责处理持久性实体、映射和资源的 ClassLoader 时必须使用“requestingBundle”。

看看: https://github.com/hibernate/hibernate-orm/blob/master/hibernate-osgi/src/main/java/org/hibernate/osgi/OsgiClassLoader.java

OsgiPersistenceProviderService 和 OsgiSessionFactoryService 都在调用服务时将“requestingBundle”添加到 ClassLoader。正如 Johanna 建议的那样,除了 requestingBundle 或 persistence.xml 文件本身的位置之外,没有真正好的方法来了解哪些 Bundle 构成了持久性单元。

不过,我很想听听有关如何更好地支持这样的设置的想法。我最初在清单中使用了额外的元数据来表示“我是持久性单元 x 的一部分”,但从未真正有时间仔细考虑。

绝对使用上面推荐的解决方案,使用 ClassLoaderHelper。这是一个完全临时的 hack,将在 ORM 5 中消失。它的存在纯粹是由于 ORM 4 的静态特性。

【讨论】:

感谢 brmeyer 提供的宝贵信息,但我们已迁移到 jdbc。【参考方案2】:

看看 org.hibernate.internal.util.ClassLoaderHelper。

您所要做的就是将 ClassLoader 替换为能够解析实体类的 ClassLoader。 Hibernate-Osgi 也将其设置为 OSGI ClassLoader(参见 org.hibernate.osgi.HibernateBundleActivator)。所以建议如下:

BundleWideClassLoader cl = new BundleWideClassLoader();

if (ClassLoaderHelper.overridenClassLoader != null 
    && ClassLoaderHelper.overridenClassLoader instanceof OsgiClassLoader) 

    OsgiClassLoader ocl = (OsgiClassLoader)ClassLoaderHelper.overridenClassLoader;
    for (Bundle b : cl.getBundles()) 
        ocl.addBundle(b);
    
 else 
    ClassLoaderHelper.overridenClassLoader = new BundleWideClassLoader();

我通过覆盖执行此例程并返回 super.buildSessionFactory 的 buildSessionFactory 将其放入我的 HibernateConfiguration 类。

BundleWideClassLoader 看起来像这样

public class BundleWideClassLoader extends ClassLoader


@Override
protected Class<?> findClass(String name) throws ClassNotFoundException 
    for (BundleClassLoader cl : this.getAllClassLoader()) 
        try 
            Class clazz = cl.findClass(name);
            return clazz;
         catch (Exception ex) 
        
    
    throw new ClassNotFoundException(name);


@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException 
    Class clazz = this.findClass(name);
    if (resolve) 
        this.resolveClass(clazz);
    
    return clazz;


@Override
public URL findResource(String name) 
    for (BundleClassLoader cl : this.getAllClassLoader()) 
        URL ret = cl.findResource(name);
        if (ret != null) 
            return ret;
        
    
    return null;


/**
 * Returns a list of all available BundleClassLoader.
 *
 * @return classloader
 */
public HashSet<BundleClassLoader> getAllClassLoader() 
    //
    // Do some magic here to get your ClassLoaders from all of your Bundles
    //


/**
 * Returns a list of all bundles which are registered in this BundleWideClassLoader.
 *
 * @return list of managed bundles
 */
public HashSet<Bundle> getBundles() 
    HashSet<Bundle> bundles = new HashSet<>();
    for (BundleClassLoader cl : this.getAllClassLoader()) 
        bundles.add(cl.getBundleContext().getBundle());
    
    return bundles;



最后是 BundleClassLoader:

public class BundleClassLoader extends ClassLoader

/**
 * Bundle context.
 */
private BundleContext context;

/**
 * Constructor.
 * @param ctx Bundle Context
 */
public BundleClassLoader(BundleContext ctx) 
    this.context = ctx;


@Override
protected Class<?> findClass(String name) throws ClassNotFoundException 
    return this.context.getBundle().loadClass(name);


@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException 
    Class clazz = this.findClass(name);
    if (resolve) 
        this.resolveClass(clazz);
    

    return clazz;


@Override
public URL findResource(String name) 
    return this.context.getBundle().getResource(name);


/**
 * Returns bundle context.
 * @return bundle context
 */
public BundleContext getBundleContext() 
    return this.context;



我建议在每个 BundleActivators 中创建一个新的 BundleClassLoader 并将其添加到某种注册表中,以便 BundleWideClassLoader 可以从那里获取 BundleClassLoader 列表。当 bundle 停止或删除时,不要忘记删除 BundleClassLoader。

【讨论】:

【参考方案3】:

如果您在多个捆绑包上使用 Hibernate,则始终会出现此问题。在 Hibernate 配置中,您无法确定在哪个 Bundle 中可以找到映射文件和 pojo 类文件。 Hibernate 不使用 OSGI 为此提供的机制。因此,hibernate 只能找到与 Hibernate 库位于同一捆绑包中的映射文件和类。

我不知道是否有针对此问题的专业解决方案(第三方产品)。

解决这个问题有两种可能:

    忘记您的片段包,将所有 Hibernate 库、映射文件、pojo、使用 Hibernate/HQL 的所有数据库的类放在一个包中。使用不同的hibernate.cfg.xml文件时可以在不同的数据库之间切换;每个数据库都有自己的配置文件。这些 hibernate.cfg.xml 文件可以在包之外。

    编写你自己的扩展 org.hibernate.cfg.Configuration 的配置类,在这个类中你必须

    编写自己的类加载器,即使在其他包中也能找到 pojo 类 覆盖 addResource(String resourceName, ClassLoader classLoader),使其在其他包中也能找到资源 覆盖 doConfigure 和 buildSessionFactory 以便它们使用您的类加载器而不是标准类加载器(使用 Thread.setContextClassLoader 并从超类调用方法,即从标准 Hibernate 配置类)。 覆盖所有其他返回 Configuration 实例的方法,以便它们返回 Configuration 类的实例,而不是 Hibernate Configuration 类的实例。

我们做了解决方案 2。这有点工作,但现在运行良好。 (想一想,当再次更改 Hibernate 版本时,可能需要做一些工作。)

【讨论】:

以上是关于具有运行时 pojos 的带有 Hibernate 的 OSGi 片段包的主要内容,如果未能解决你的问题,请参考以下文章

hibernate的优缺点

框架的优缺点

在 Hibernate 中映射 2 个具有相同结构的表

如何使用 maven 配置 hibernate-tools 以生成 hibernate.cfg.xml、*.hbm.xml、POJO 和 DAO

Hibernate 对象关系映射文件

hibernate框架学习之数据模型-POJO