Good day guys, I'm programming an OSGi bundle in charge of authenticating users. For design reasons I'm obligated to perform queries over multiple database schemas (these schemas can be created or deleted dynamically). I'm running MySQL as storage engine.
Somehow I need to be able to create on demand entity managers for those schemas, but I haven't been successful in my attempts. Here is what I have tried that has got me closer to what I need:
Creating a persistence unit (Eclipselink) using a JTA data source that can, in fact, establish a database connection to the default schema. However, when I try to override any property, say, the javax.persistence.jdbc.url. However, it keeps pointing to the default schema always.
I believe I'm not overriding correctly the property, or that JTA datasource properties cannot be modified from EntityManager to EntityManager. Here is how I create the EntityManagers:
Map<String, String> dbProps = new HashMap<String, String>();
dbProps.put("javax.persistence.jdbc.url","jdbc:mysql://mydomain:3306/mydynamicdb);
EntityManagerFactory fact = Persistence.createEntityManagerFactory("myPersistenceUnit", dbProps);
EntityManager myEM = fact.createEntityManager();
At the end, they all keep poiting to the default schema, so my questions are:
Is this an efficient approach for dynamic EntityManager handling? if so, how can I override effectively the schema property?
Is there any other alternative besides the overriding?
I thank you in advance for any guidance you may provide.
If you want to use EclipseLink in OSGi you must use the Gemini JPA project that wraps EclipseLink and create and register an EntityManagerFactory and an EntityManagerFactoryBuilder services for your PU bundle.
If want to share the jdbc connections between PUs you can use JDBC services provided by Gemini DBAccess
You should be able to acquire the EntityManagerFactory as an OSGi service, you can (LDAP) filter the correct one using the service property osgi.unit.name as in:
ServiceReference[] refs = null;
String filter = "(osgi.unit.name=myPersistenceUnit)";
ServiceReference[] refs = ctx.getServiceReferences(EntityManagerFactory.class.getName(), filter);
//Should only be one reference, check (throw exception etc)
return (EntityManagerFactory)ctx.getService(refs[0]);
You can check that all Persistence Units are present, by listing the EntityManagerFactory services (with properties) in your OSGi shell.
Related
I am new at Hibernate.
Into my code, the connection to the DB is managed with the Hikari data source.
My code is right now multitenant, but it manages the same hibernate dialect for all tenants.
Is it possible to create a configuration where each tenant can use a different dialect?
The type of dialect can be provided as a tenant's property.
This is an example of the entityManagerFactory:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
Map<String, Object> jpaProperties = new HashMap<>();
jpaProperties.put(..., ...);
jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, "myDialect");
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setPackagesToScan(new String[] {MyEntity.class.getPackage().getName()});
emfBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emfBean.setJpaPropertyMap(jpaProperties);
return emfBean;
}
Edit
I was looking to this solution: it suggests to create a duplicated LocalContainerEntityManagerFactoryBean for each dialect.
What I do not understand is how can I tell when using one EntityManager (MySQL) and when the other one (Postgres or MsSQL): the solution discriminates the entities (each entity has its own DB) but in my case, all entities are on all DBs. Is the tenant that discriminates.
For example: if I create a second instance of LocalContainerEntityManagerFactoryBean (i.e. msSQLEntityManagerFactory()) with setted the dialect for SQL Server, the application fails to start with:
Application failed to start due to an exceptionorg.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'javax.persistence.EntityManagerFactory' available:
expected single matching bean but found 2:
msSQLEntityManagerFactory,entityManagerFactory
That's not really possible as the dialect affects certain quoting rules etc. which you can't just "swap out" at runtime based on a tenant identifier. Just create two persistence units, each pointing to a different data source for every database type. You will have to somehow lookup the appropriate EntityManager/EntityManagerFactory based on your tenant identifier which makes it a bit harder when you want to use Spring Data JPA as that requires a compilation static name reference for the entity manager factory. Maybe you can create a custom EntityManagerFactory that delegates all method calls to the appropriate instance based on the tenant identifier. Overall, this is not so easy and you will probably have to do a lot of trial and error.
IMO it would be better to have a separate application deployment with separate configuration if possible per database type.
I finally managed to find a solution for this problem.
I managed to get around the problem with the dialects by having an entity manager factory for each dialect (in this case MySQL, Postgres and MS SQL Server).
Create a bean for EntityManagerFactory and return a proxy of that interface and in the handler, based on your logic, you can switch which emf to use to suit the used data source.
I have created a video for this because it seems like there is no documentation online.
Session Scoped Connection
It is pretty similar to what you're trying to achieve but in my case the users are providing the credentials, so it's even more complicated.
I am creating a Drools stateful session as described in the JBPM persistence documentation:
http://docs.jboss.org/jbpm/v5.1/javadocs/org/drools/persistence/jpa/JPAKnowledgeService.html
However, I came across the following exception
javax.persistence.TransactionRequiredException: joinTransaction has been called on a resource-local EntityManager which is unable to register for a JTA transaction.
My code is :
EntityManagerFactory emf = Persistence.createEntityManagerFactory("metadata.model");
Environment env = KnowledgeBaseFactory.newEnvironment();
env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, emf);
env.set(EnvironmentName.TRANSACTION_MANAGER, TransactionManagerServices.getTransactionManager());
env.set(EnvironmentName.TRANSACTION, TransactionManagerServices.getTransactionManager());
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieBase kBase = kContainer.getKieBase();
StatefulKnowledgeSession kSession = JPAKnowledgeService.newStatefulKnowledgeSession(kBase, null, env);
The exception is thrown at the last line. Beforehand, I have bound the JDBC JTA Datasource as described in the aforementioned documentation.
PoolingDataSource ds = new PoolingDataSource();
ds.setUniqueName("jdbc/BitronixJTADataSource");
ds.setClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
ds.setMaxPoolSize(3);
ds.setAllowLocalTransactions(true);
ds.getDriverProperties().put("user", "root");
ds.getDriverProperties().put("password", "****");
ds.getDriverProperties().put("URL", "jdbc:mysql://localhost:3306/metadatadb");
ds.init();
I am using the EclipseLink persistence provider alongside the MySQL JDBC driver.
Finally, I have made it work. The most important mistake I was making was that I was trying to use EclipseLink as JPA provider. This approach will not work, since besides the custom persistency classes, Drools uses two other persistency-annotated classes: org.drools.persistence.info.SessionInfo and org.drools.persistence.info.WorkItemInfo. These two contain Date fields which are not annotated with the JPA Temporal annotation. They appear to be tailored specifically for Hibernate.
Another important aspect that came to my attention is the need to add the following line after setting the environment variable:
env.set(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES,
new ObjectMarshallingStrategy[] {
new JPAPlaceholderResolverStrategy(domainEnv),
new SerializablePlaceholderResolverStrategy(
ClassObjectMarshallingStrategyAcceptor.DEFAULT) });
This is performed in order to announce your intention to persist the current session state using JPA.
The exception I mentioned above was, however, due to the fact that EclipseLink was creating a "ResourceLocal" transaction wrapper even though JTA was explicitly specified in persistence.xml. This is due to the fact that there was no target-server property specified. Consequently, there were no external transaction controllers attached to the database session that was created and the wrapper that was provided simply couldn't support the joinTransaction operation. To work around this issue, add the following line to your persistence.xml file:
<property name="eclipselink.target-server" value="JBoss"/>
and before initializing the datasource, add:
Configuration conf = TransactionManagerServices.getConfiguration();
conf.setJndiUserTransactionName("java:/TransactionManager");
Of course, I am presuming that BTM is being used.
We have an application which uses multiple databases to store the same data for different countries.
For example a Subscription object might be associated with Germany or Spain. If it's a German subscription, it needs to be stored in a different database to the Spanish subscriptions. The databases are identical in structure, but they have different contents.
We are running on jboss 5, and have a different datasource config (*ds.xml) file for each one, generated dynamically at startup. They are stored in JNDI - so we have DataSourceDE, DataSourceES, etc.
Here's how it should work: if a request comes in saying 'fetch subscription 17 for Germany' then I calculate that the datasource should be DataSourceDE and use JPA / hibernate to go fetch that object from the correct database. There will be a subscription 17 in the Spanish database too, which I don't want in this example.
I can generate the persistence.xml automatically to create the extra persistence units for the datasources, but the Subscription class is annotated with the following:
#PersistenceContext(unitName="core")
This is not going to work - how can I set the persistence context on the java object dynamically?
What you are trying to achieve is known as Multi-Tenancy. Here is a perfectly suitable tutorial for your question to make it work.
The main idea is to use a Stateless session bean which has a reference to both persistence units. Depending on what has to be done, this bean does a lookup to call the corresponding EntityManager. Furthermore here:
Multi-Tenancy With EJB 3.1 and JPA 2.0
You can change the persistence context for the EntityManager at runtime like this:
EntityManagerFactory emf =
Persistence.createEntityManagerFactory(persistenceUnitName);
EntityManager em = emf.createEntityManager();
I am trying to enable my web application to use one (MySQL) schema per user.
I am using Spring with JPA along with Hibernate.
Is the Hibernate Multi-tenancy concept relevant?
The way I am trying to do it is
(a) have a EntityManagerFactory per HTTPSession and
(b) set the schema in it at login.
I have figured out how to do (b), but I still have issues doing (a).
I tried putting #Scope(WebApplicationContext.SCOPE_SESSION), but what about the global EntityManagerFactory?
Any help?
You can implement your own ConnectionProvider and there make extra settings.
But I think that you have large problems with the architecture your application if you want to do one schema per user.
UPD1
If you use spring. You can try to declare a bean with own impl of ConnectionProvider in Session scope.
But there is a big problem. Hibernate make the ConnectionProvider. It means that you have to impl own ServiceRegistry (with working through Spring) and override StandardServiceRegistryBuilder, and impl EntityManagerFactoryBuilder (based on EntityManagerFactoryBuilderImpl but with your StandardServiceRegistryBuilder).
When new session have been created it'll use ConnectionProvider to create the connection (Probably you'll have to override some classes).
This is really not suggested.. This will harm you later, you can't do differnt schema for diffrent user. You can always create you own properties to the connection..
Configuration cfg = new Configuration();
cfg.configure();
System.setProperty("hibernate.connection.password",pass);
System.setProperty("hibernate.connection.username",usr);
System.setProperty("hibernate.connection.driver_class", driver_class);
System.setProperty("hibernate.connection.url", driver_url);
System.setProperty("hibernate.dialect", dialect);
// etc, etc, for all properties
cfg.setProperties(System.getProperties());
sessionFactory = cfg.buildSessionFactory();
It can be something like that.. But this is WRONG!
I'm developping a program that uses JPA, and I deployed it in a single jar. I wish to modify (or ask to the user, in an menĂº item) the configuration of the connection data with the database server.
It's possible to modify the data (user, password, ip) of the server "on the fly"?
(I apologize my bad english)
how about making a HashMap<String,Object> containing your options and passing it to Persistence.createEntityManagerFactory("unitName",map)?
From the Docs:
public static EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, Map properties)
Create and return an EntityManagerFactory for the named persistence unit using the given properties.
Source
The JPA spec doesn't allow for dynamic modification of persistence-units. Some implementations may provide an implementation-specific way of defining a persistence-unit dynamically. With DataNucleus JPA we do it as per the foot of this page