Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load Hibernate entities from external JAR

Tags:

java

hibernate

I am trying to load entities from several jar files. What I managed to do is

  1. configure hibernate

    private void configure(File[] moduleFiles)
    {
    Configuration configuration = new Configuration()
        .setProperty("hibernate.connection.url", getConnectionString())
        .setProperty("hibernate.connection.username", "user")
        .setProperty("hibernate.connection.password", "pass")
        .setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbc.JDBCDriver")
        .setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect")
        .setProperty("hibernate.archive.autodetection", "class,hbm")
        .setProperty("exclude-unlisted-classes", "false")
        .setProperty("hibernate.hbm2ddl.auto", "update");
    
    if (moduleFiles != null) {
        for (File f : moduleFiles) {
            configuration.addJar(f);
        }
    }
    
    ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
    this.sessionFactory = configuration.buildSessionFactory(serviceRegistry);
    }
    

so the entities should be loaded from moduleFiles array. In logs I can see:

    2015-08-25 20:52:12 INFO  Configuration:837 - HHH000235: Searching for mapping documents in jar: ProgramInfo.jar
    2015-08-25 20:52:12 INFO  Configuration:837 - HHH000235: Searching for mapping documents in jar: SampleModule.jar
  1. The entity in external jar

    @Entity
    @Table(name = "PROGRAMINFO_DATA", schema = "PUBLIC", catalog = "PUBLIC")
    @NamedQueries({@NamedQuery(name = "PrograminfoDataEntity.findByWindowInfo", query = "FROM PrograminfoDataEntity WHERE PROCESSPATH = :pp AND WINDOWTITLE = :wt AND DAY = :d")})
    public class PrograminfoDataEntity implements SVEntity {
        private long id;
        private Date day;
        private String processname;
        private String processpath;
        private String programname;
        private String windowtitle;
    
        // getters setters etc.
    }
    
  2. persistence.xml in external jar (META-INF directory)

    <persistence-unit name="ProgramInfoPersistenceUnit">
        <class>com.antara.modules.programinfo.db.model.PrograminfoDataEntity</class>
    </persistence-unit>
    

  3. Query with above entity usage

        Session session = openSession();
        Query q = session.getNamedQuery("PrograminfoDataEntity.findByWindowInfo");
        q.setParameter("pp", windowInfo.getProcessPath());
        q.setParameter("wt", windowInfo.getWindowTitle());
        q.setDate("d", date);
    
        PrograminfoDataEntity result = (PrograminfoDataEntity) q.uniqueResult();
        closeSession(session);
    

which threw an exception:

org.hibernate.MappingException: Named query not known: PrograminfoDataEntity.findByWindowInfo
    at org.hibernate.internal.AbstractSessionImpl.getNamedQuery(AbstractSessionImpl.java:177)
    at org.hibernate.internal.SessionImpl.getNamedQuery(SessionImpl.java:1372)
    at com.antara.modules.programinfo.db.dao.PrograminfoDao.findByWindowInfo(PrograminfoDao.java:26)
    at com.antara.modules.programinfo.ProgramInfoImpl.run(ProgramInfoImpl.java:84)

The question is why hibernate didn't loaded the annotated entity from jar? The exception is thrown not only by named query, but any other operation with entity. There are no errors before usage of this entity. Local entities are loaded properly.

EDIT:

After some changes I managed to recognize entity by Hibernate

DEBUG AnnotationBinder:601 - Binding entity from annotated class: com.antara.modules.programinfo.db.model.PrograminfoDataEntity
DEBUG QueryBinder:93 - Binding named query: PrograminfoDataEntity.findByWindowInfo => FROM PrograminfoDataEntity ....

But when I try to use the entity I still get exception:

ERROR AssertionFailure:61 - HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): java.lang.ClassNotFoundException: com.antara.modules.programinfo.db.model.PrograminfoDataEntity
ERROR Main:114 - PersistentClass name cannot be converted into a Class
...
Caused by: java.lang.ClassNotFoundException: com.antara.modules.programinfo.db.model.PrograminfoDataEntity

The change was: passing Configuration to each "module" that is inside jar and add Annotated Class (by module I mean SPI service with method invoked at startup)

@Override
public void configureDB(Configuration configuration) {
    configuration.addAnnotatedClass(PrograminfoDataEntity.class);
}
like image 493
ANTARA Avatar asked Aug 25 '15 22:08

ANTARA


2 Answers

After 3 days of trials I have found the solution: Hibernate loades classes using reflection mechanism by ContextClassLoader

Thread.currentThread().getContextClassLoader();

so I set ContextClassLoader to ClassLoader of PrograminfoDataEntity

Thread.currentThread().setContextClassLoader(PrograminfoDataEntity.class.getClassLoader());

and it solved all NoClassDefFound, ClassCastException and similar errors

like image 141
ANTARA Avatar answered Oct 06 '22 02:10

ANTARA


According to javadoc and implementation code Hibernate only read *.hbm.xml at Configuration.addJar method

I guess Hibernate doesn't auto scan jars because of restrictions in JPA Specification

I've done auto scan of jars in hibernate extending the scanner and adding the jars on it. But you should use JPA api to that. Something like:

Map<String, Object> map = new HashMap<>();
map.put("hibernate.connection.url", getConnectionString());
map.put("hibernate.connection.username", "user");
map.put("hibernate.connection.password", "pass");
map.put("hibernate.connection.driver_class", "org.hsqldb.jdbc.JDBCDriver");
map.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
map.put("hibernate.archive.autodetection", "class,hbm");
map.put("exclude-unlisted-classes", "false");
map.put("hibernate.hbm2ddl.auto", "update");
//Property to change scanner
map.put("hibernate.ejb.resource_scanner", "me.janario.MyScanner");

EntityManagerFactory emf = Persistence.createEntityManagerFactory("ProgramInfoPersistenceUnit", map);
SessionFactory sessionFactory = ((HibernateEntityManagerFactory) emf).getSessionFactory();

And the scanner something like:

public class MyScanner extends StandardScanner {
    @Override
    public ScanResult scan(PersistenceUnitDescriptor persistenceUnit, ScanOptions scanOptions) {
        try {
            persistenceUnit.getJarFileUrls()
                    .add(new URL("file:/path/my.jar"));
            return super.scan(persistenceUnit, scanOptions);
        } catch (MalformedURLException e) {
            throw new IllegalStateException(e);
        }
    }
}
like image 43
Janario Oliveira Avatar answered Oct 06 '22 02:10

Janario Oliveira