Hibernate 5 - programmatically adding HBM mapping to the configuration - no named queries found


#1

In the process of migrating an old app from JBoss 5.1.0GA to WildFly 13.
On the previous server we were using hibernate3 while on WildFly 13 we’re trying to use hibernate 5.

A bit on the app - it’s built as a multi-tenant app so it has tables that are common to all the clients and tables that are client specific. These client specific tables are suffixed with the clientId, so they would read Document_2, Document_3, …, Document_n (e.g.).

The common tables definitions are in a .sar archive, under the hibernate.cfg.xml file.

For the common table mappings there is another class like so:

> @Singleton
@Startup
public class MyHibernateService
	implements
	DynamicMBean {

	private MBeanServer platformMBeanServer;
	private ObjectName objectName = null;


	@PostConstruct
	public void start() {
		new Configuration().configure().buildSessionFactory();
		this.registerJMX();
	}


	private void registerJMX() {
		try {
			this.objectName = new ObjectName("jboss.jca:service=HibernateFactory,name=HibernateFactory");
			this.platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
			this.platformMBeanServer.registerMBean(this, this.objectName);
		} catch (Exception e) {
			throw new IllegalStateException("Exception during JMX registration :" + e);
		}
	}
....
}

This class binds the hibernate factory for the common tables under a JNDI name so it can be retrieved later.

The client specific tables definitions / mapping are derived from a template. On app boot we build session factories specific for each client. The way we do this is that we parse the template mapping and replace some sections with the client specific one. The template would read

where TABLENAME that would get replace at boot with e.g. Document_2.

This SessionFactoryManager class that does all the replacement looks like below:

> if (SessionFactoryManager.LOGGER.isDebugEnabled()) {
	SessionFactoryManager.LOGGER.info("build custom SessionFactory for  datasource: " + databaseConfig);
}

Configuration cfg = new Configuration();

// build all mappings
for (Class c : mappingClasses) {

	try {
		Method m = c.getMethod("getInstance", (Class[])null);
		Helper dao = (Helper)m.invoke(null, (Object[])null);

		String tableName = dao.getTableName(id);

		String mapping = dao.getMappping();

		if (mapping == null) {
			throw new DAOException(DAOException.TYPE.SESSION_FACTORY, "Mapping not available from class: " + c.getName());
		}

	
		cfg.addXML(mapping);
	} catch (Exception e) {
		throw new DAOException(DAOException.TYPE.SESSION_FACTORY, e);
	}
}

cfg.setProperty("hibernate.dialect", databaseConfig.getDatabaseDialect().getClass().getName());

if (StringTools.isValidString(databaseConfig.getDatabaseJNDI())) {
	cfg.setProperty("hibernate.connection.datasource", databaseConfig.getDatabaseJNDI());
} else {
	cfg.setProperty("hibernate.connection.url", databaseConfig.getDatabaseURL());
	cfg.setProperty("hibernate.connection.driver_class", databaseConfig.getDatabaseDriverClass());
	cfg.setProperty("hibernate.connection.username", databaseConfig.getDatabaseUser());
	cfg.setProperty("hibernate.connection.password", databaseConfig.getDatabasePassword());
}

if (showSQL) {
	cfg.setProperty("hibernate.show_sql", "true");
	cfg.setProperty("hibernate.format_sql", "false");
}

if (operation == OPERATION.RECREATE) {
	cfg.setProperty("hibernate.hbm2ddl.auto", "update");
} else {
	// With create-drop, the database schema will be dropped when the SessionFactory is closed explicitly
	// this is necessary in order to remove client tables if something goes wrong at client creation time
	cfg.setProperty("hibernate.hbm2ddl.auto", "create-drop");
}


SessionFactory sf = cfg.configure().buildSessionFactory();

System.out.println(cfg.getNamedQueries());

The problem is that, although I see that cfg.addXML(String xml) is deprecated, the cfg.getNamedQueries() returns an empty map, as if the mapping is not loaded onto the configuration.

All the above code works fine for hibernate3.

I also tried changing:

SessionFactory sf = cfg.configure().buildSessionFactory();

to

> StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder();
builder.applySettings(cfg.getProperties());
MetadataSources metaData = new MetadataSources(builder.build());
Metadata buildMetadata = metaData.buildMetadata(builder.build());
SessionFactory sf = buildMetadata.buildSessionFactory();

but to no avail.

Any ideas ?


#2

Do it like this for HBM:

String mapping = dao.getMappping();

if (mapping == null) {
	throw new DAOException(DAOException.TYPE.SESSION_FACTORY, "Mapping not available from class: " + c.getName());
}

configuration.addResource(
	mapping,
	getClass().getClassLoader()
);

or for JPA mappings files:

String mapping = dao.getMappping();

if (mapping == null) {
	throw new DAOException(DAOException.TYPE.SESSION_FACTORY, "Mapping not available from class: " + c.getName());
}

try (InputStream is = Thread.currentThread().getContextClassLoader()
		.getResourceAsStream( xmlFile )) {
	configuration.addInputStream( is );
}
catch (IOException e) {
	throw new IllegalArgumentException( e );
}

#3

Changing

cfg.addXML(mapping);

to

cfg.addResource(mapping, SessionFactoryManager.class.getClassLoader());

leads to a mapping not found exception:

org.hibernate.boot.MappingNotFoundException: Mapping (RESOURCE) not found : <?xml version=\"1.0\"?>

This is correct, in some sorts, because that actual mapping file does not exist under my classpath since it’s derived from the template. What I’m trying to achieve is to dynamically add the “derived” mapping to the configuration.

To recap: every tenant of the app has an id. Every tenant has its own tables - suffixed with their id. The goal would be to create a sesion factory for every client, starting from a series of mapping templates.
Each mapping template has values replaced, at app boot, so as to create dynamic mappings that conform to every tenants’ tables.

The mapping template would read (e.g.):

<class name="com.test.Document" table="TABLENAME">

At app boot the “TABLENAME” would get replace with Document_“tenantId” and then there would be a string holding the XML mapping. What I would like to do (and this was possible in hibernate3) would be to dynamically add this “string” mapping to the configuration, without actually having the specific mapping in the app EAR.

Code-wise, this would look like so:

for (Class c : mappingClasses) {
// iterating over all the classes that are client specific
try {
Method m = c.getMethod(“getInstance”, (Class[])null);
DAOHelper dao = (DAOHelper)m.invoke(null, (Object[])null);

String tableName = dao.getTableName(id);

    // getting the template mapping associated with the respective class/table

String mapping = dao.getMappping();

if (mapping == null) {
throw new DAOException(DAOException.TYPE.SESSION_FACTORY, "Mapping not available from class: " + c.getName());
}

   // replacing some keywords in the template (such as TABLENAME) with specific tenant values
   mapping = mapping.replace(TABLE_NAME_KEY, tableName).replace(SEQUENCE_NAME_KEY,
  			dao.getSequenceName(databaseConfig.getDatabaseDialect(), tableName));

   // at this point the mapping is changed from the "template" form to the "client" form
   // dynamically adding the new mapping (client form) to the configuration
  cfg.addXML(mapping);

}

Hope the above sheds some more light on my situation.

Thanks !


#4

Fixed it by changing

cfg.addXml(String xml)

to

cfg.addInputStream(new ByteArrayInputStream(mapping.getBytes()));


#5

The mapping can be:

  • a URL String representation (e.g. file://…, classpatch://…)

  • the full path to of the resource inside the jar, like this:

    org/hibernate/test/array/A.hbm.xml

I suppose the mapping was just A.hbm.xml in your case.


#6

The template mapping, yes, was a resource inside the jar.
The actual, derived template, was obtained by doing some replacements on the template mapping.


#7

As long as the resource name is properly constructed and references the mapping file in your jar, it should work.


#8

@vlad Hello, hibernate support is dynamically created entity when runtime


#9

@xiaoming_cheng Would you mind elaborating your question? I didn’t understand what you meant.


#10

@vlad
Similar to dynamic models
,but what I want to achieve is to call a method to dynamically add a Domain Model after the application starts.
at this time, the sessionFactory has been created.
example:
Metadata metadata = metadataBuilder.build();
metadata.addResource( “org/hibernate/example/Product.orm.xml” ) ;


#11

I don’t think that’s possible. Once the SessionFactory is built, you can no longer register new entities. You need to stop the current SessionFactory and create a new one.


#12

In this case, the entities corresponding to the two sessionFactorys cannot communicate with each other.


#13

They don’t have to. The second SessionFactory will contain everything the first one contained plus the new entities.