OSGi Fragment包含Hibernate,具有Runtime pojos

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

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

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

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

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

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

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

我不知道这个问题是否有任何专业解决方案(第三方产品)。

解决此问题有两种可能性:

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

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

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

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

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

您所要做的就是用ClassLoader替换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(); } 

我通过重写buildSessionFactory将它放到我的HibernateConfiguration类中,它执行此例程并返回super.buildSessionFactory。

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 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 getBundles() { HashSet 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列表。 当捆绑被停止或移除时,不要忘记删除BundleClassLoader。

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

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

OsgiPersistenceProviderService和OsgiSessionFactoryService都在调用服务时将“requesBundle”添加到ClassLoader。 正如Johanna建议的那样,除了requestedBundle或persistence.xml文件本身的位置之外,还没有一种方法可以知道哪些Bundles构成了持久性单元。

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

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