I have a fairly standard Java EE6 web application using JPA 2 with dependency injection connecting to a MySQL database and everything is working fine. What I would like to do now is have this application interact with the databases of other applications we have installed at a clients site - essentially acting as a single point of control for our other application installs.
What I'm struggling with is how best to perform the interaction with the other databases. Ideally I would like to create an EntityManager for each install and interact using JPA but I can't see any way to set this up. I may, for example, have 5 installs (and therefore databases) of one application type and the master control application won't know about the other installs until runtime. This seems to preclude using dependency injection of an EntityManager and all the automatic transaction demacation etc etc. The alternative option is to just create a DataSource and do the interactions manually. While flexible this clearly requires a lot more effort.
So, my question really is how do I best tackle this problem?
I'm also looking into this, and so far I have found the following blog post that describes a way to do it
http://ayushsuman.blogspot.com/2010/06/configure-jpa-during-run-time-dynamic.html :
Removed all your database properties from persistance.xml
<persistence>
<persistence-unit name="jpablogPUnit" transaction-type="RESOURCE_LOCAL">
<class>com.suman.Company</class>
</persistence-unit>
</persistence>
Changed your java file where you are configuring entityManager, in our case TestApplication.java
package com.suman;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.apache.log4j.Logger;
/**
* #author Binod Suman
*/
public class TestApplication {
Logger log = Logger.getLogger(TestApplication.class);
public static void main(String[] args) {
TestApplication test = new TestApplication();
test.saveCompany();
}
public void saveCompany(){
log.info("Company data is going to save");
EntityManagerFactory emf;
Map properties = new HashMap();
properties.put("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
properties.put("hibernate.connection.url", "jdbc:mysql://localhost:3306/sumandb");
properties.put("hibernate.connection.username", "root");
properties.put("hibernate.connection.password", "mysql");
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.show-sql", "true");
//emf = Persistence.createEntityManagerFactory("jpablogPUnit");
emf = Persistence.createEntityManagerFactory("jpablogPUnit",properties);
EntityManager entityManager = (EntityManager) emf.createEntityManager();
entityManager.getTransaction().begin();
Company company = new Company(120,"TecnoTree","Espoo, Finland");
entityManager.persist(company);
entityManager.getTransaction().commit();
log.info("Company data has been saved");
}
}
Related
I'm using spring boot and it perfectly makes me entity manager. And I decided to test getting session factory from the entity manager and to use it for an example. But I get the next problem:javax.persistence.TransactionRequiredException: no transaction is in progress
properties
spring.datasource.url= jdbc:postgresql://localhost:5432/ring
spring.datasource.username=postgres
spring.datasource.password=root
spring.jpa.show-sql = false
spring.jpa.properties.hibernate.format_sql=false
#Note: The last two properties on the code snippet above were added to suppress an annoying exception
# that occurs when JPA (Hibernate) tries to verify PostgreSQL CLOB feature.
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false
spring.jpa.properties.hibernate.current_session_context_class = org.springframework.orm.hibernate5.SpringSessionContext
service class
package kz.training.springrest.service;
import kz.training.springrest.entity.User;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
#Service
public class UserService {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void insertUser(User user) {
SessionFactory sessionFactory = entityManager.unwrap(Session.class).getSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.save(user);
}
}
runner
package kz.training.springrest.run;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#SpringBootApplication
#EntityScan("kz.training.springrest.entity")
#EnableTransactionManagement
#ComponentScan(basePackages="kz.training.springrest")
public class SpringrestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringrestApplication.class, args);
}
}
Do you have any ideas how to solve it?
I don't quite understand why you're making your service method so unnecessarily complex. You should simply be able to do it this way
#Transactional
public void insertUser(User user) {
entityManager.persist( user );
}
If there are points where you need access to the native Hibernate Session you can simply unwrap and use the Session directly like this:
#Transactional
public void doSomethingFancyWithASession() {
Session session = entityManager.unwrap( Session.class );
// use session as needed
}
The notion here is that Spring provides you an already functional EntityManager instance by you using the #PersistenceContext annotation. That instance will safely be usable by the current thread your spring bean is being executed within.
Secondly, by using #Transactional, this causes Spring's transaction management to automatically make sure that the EntityManager is bound to a transaction, whether that is a RESOURCE_LOCAL or JTA transaction is based on your environment configuration.
You're running into your problem because of the call to #getCurrentSession().
What is happening is Spring creates the EntityManager, then inside your method when you make the call to #getCurrentSession(), you're asking Hibernate to create a second session that is not bound to the transaction started by your #Transactional annotation. In short its essentially akin to the following:
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
Session aNewSession = entityManager.unwrap( Session.class )
.getFactory()
.getCurrentSession();
// at this point entityManager is scoped to a transaction
// aNewSession is not scoped to any transaction
// this also likely uses 2 connections to the database which is a waste
So follow the paradigm I mention above and you should no longer run into the problem. You should never need to call #getCurrentSession() or #openSession() in a Spring environment if you're properly allowing Spring to inject your EntityManager instance for you.
I have same your error, when I deploy my spring boot app to WebLogic Server.
(Even It works fine if I run it directly via Eclipse (Or deploy to Tomcat) ).
I solved the problem by adding #EnableTransactionManagement to UserService.
salved my problem using in hibernate conf "hibernate.allow_update_outside_transaction","true"
I have a requirement to connect to multiple databases to query and consolidate data before returning it to the users. Is there a way of doing it using MyBatis and cdi? I looked into using DatabaseIdProvider and having multiple environment configurations but looks like they will not work for this scenario. With multiple environment configurations I can create different sql session factories, but how will cdi for mappers work in that case? I would like to use cdi as much as possible. Will MyBatis Guice help with this? I have looked at a similar question but I am unable to determine if Guice will help in this case where I need to query multiple databases in the same service call.
You will need 2 SqlSessionFactories defined. Separate environments may suit your needs, but it's also possible to use completely separate configurations and object / class hierarchies. Regardless, you can pass each one as needed in the configuration for your mappers or the mapper scanner.
See the documentation for the various ways to configure your mappers. Note that all of them allow you to specify the SqlSessionFactory, if needed. If each configuration is using the same mappers, you might not be able to use the scanner approach since that I think that will use the same names for mappers from different SqlSessionFactories. In that case you will have to manually configure your mappers using different names for different configs.
Then in your service you can do something like:
public class FooServiceImpl implements FooService {
#Autowired
#Qualifier("fooMapperDB1")
private FooMapper fooMapper1;
#Autowired
#Qualifier("fooMapperDB2")
private FooMapper fooMapper2;
public List<Foo> doService(String id) {
List<Foo> toReturn = new ArrayList<>();
toReturn.add(fooMapper1.getFoo());
toReturn.add(fooMapper2.getFoo());
return toReturn;
}
}
Note that you need to be careful here with transactions. You probably want the service to be transactional and then use distributed XA datasources for both DB connections.
The environment configuration define datasource and transaction manager.
Then connecting to multiple databases requires at least as much SqlSessionFactory:
Reader reader = Resources.getResourceAsReader(classpathConfigFilename);
new SqlSessionFactoryBuilder().build(reader, environment, properties);
Call one for each environment
If the purpose is to consolidate data, I guess both data models are slightly different (then so are queries, mappers, typeHandler ...), then SqlSessionFactory would better be created from different mybatis-config files and use their default environment:
new SqlSessionFactoryBuilder().build(reader, properties);
How so it does not solve the issue?
A more accurate answer might be brought if you edit the question to provide more information about context, examples, what you have tried.
Guice: may of course be used, but is not required for this achievement.
EDIT:
Use Mybatis-CDI
Here is how to produce SqlSession factories (they must be #Named):
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Named;
public class SqlSessionFactoryWrapper {
private static final String BI_CONFIG = "mybatis-config.xml";
private SqlSessionFactory getFactory(final String config, final String env) throws IOException {
final Reader reader = Resources.getResourceAsReader(config);
final Properties properties = new Properties();
properties.load(Resources.getResourceAsReader("some.properties"));
return new SqlSessionFactoryBuilder().build(reader, env, properties);
}
#Produces
#ApplicationScoped
#Named("A")
public SqlSessionFactory getFactoryA() throws IOException {
return this.getBiFactory(CONFIG, "A");
}
#Produces
#ApplicationScoped
#Named("B")
public SqlSessionFactory getFactoryB() throws IOException {
return this.getFactory(CONFIG, "B");
}
}
And how to inject:
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
#Inject
#Named("A")
protected SqlSession sessionA;
#Inject
#Named("B")
protected SqlSession sessionB;
Or inject directly mapper for from SqlSession of environment B:
import org.mybatis.cdi.Mapper;
#Inject
#Named("B")
#Mapper
protected MyMapper mapper;
This is working in my app running in jBoss, but there is nothing jBoss-specific here.
Read the documentation for transaction management, this is pretty clear.
I recommend using
#Transactional(executorType = ExecutorType.REUSE)
specialy for bulk operations to prepare statements only once.
EDIT2:
mybatis.cdi annotations in snippets are valid for version 1.0.0.beta3 I was using then. In further betas they have slightly evolved, then few adaptation is required to work with final release (out for just a month).
I currently have one database connected and it is working. I would like to connect another (and eventually 2 more) databases. How do I do so? There should be a solution using only annotations and properties files.
I read this
Profile Specific Properties
and it sort of helps but I still don't know how switch from one profile to the other in the code during runtime. I'm assuming I need to be connected to one profile at a time before I try to retrieve/persist things from different databases.
I also read this question, How to use 2 or more databases with spring?, but I dont know how it works too well/ if it will apply. I'm not using a controller class and I dont know what that does. I'm also not sure how the config class they mention in the answer actually connects to the specific DO.
This is my application.properties file: (marked out username and password but its there in my file)
hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.default_schema=dbo
hibernate.packagesToScan=src.repositories.LMClientRepository.java
spring.jpa.generate-ddl=true
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
spring.datasource.username=***
spring.datasource.password=***
spring.datasource.url=jdbc:sqlserver://schqvsqlaod:1433;database=dbMOBClientTemp;integratedSecurity=false;
spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1
spring.jpa.database=dbMOBClientTemp
spring.jpa.show-sql=true
spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
This is my application file:
package testApplication;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.orm.jpa.EntityScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import fileRetrieval.InputFileParse;
import lmDataObjects.LMClientDO;
import lmDataObjects.LoadMethodDO;
import repositories.LMClientRepository;
import repositories.LoadMethodRepository;
#SpringBootApplication
#EnableJpaRepositories(basePackageClasses = LoadMethodRepository.class)
#EntityScan(basePackageClasses = LoadMethodDO.class)
#EnableCaching
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public CommandLineRunner demo(LoadMethodRepository lm_repo, LMClientRepository lmc_repo) {
return (args) -> {
List<LMClientDO> lmlist = InputFileParse.getMultiGroupfile();
List<String> uniqueMediaIds = new ArrayList(InputFileParse.getUniqueMediaIds());
for (int i = 0; i < InputFileParse.getUniqueMediaIds().size(); i ++){
lm_repo.save(new LoadMethodDO(uniqueMediaIds.get(i)));
}
for (int i = 0; i < lmlist.size(); i++){
lmc_repo.save(new LMClientDO(lmlist.get(i).getClientId(), lmlist.get(i).getMediaId()));
}
//Here is where I would like to do stuff with data from the other database that I have not connected yet
};
}
}
I also made a new properties file called application-MTS.properties and I put data for the new database in there. Still unsure of what to do with it.
spring.datasource.username=***
spring.datasource.password=***
spring.datasource.url=jdbc:sqlserver://SCHQVSQLCON2\VSPD:1433;database=dbMTS;integratedSecurity=false;
You will need to define multiple DataSource beans that each represent the various database connection resources you plan to use.
You will then need to add a TransactionManager and EntityManagerFactory bean definition for each of those DataSource beans.
If you intend to have each DataSource participate in a JTA transaction, you'll need to also consider configuring a JTA transaction manager rather than individual resource local transaction managers.
I am using Glassfish v3 server.
Usually the DB connection with EJB3 + JPA (Eclipselink) is done through injection, with #PersistenceUnit or #Persistencecontext.
However, there are 3 layers in my App :
Core (contains business logic, entities, exception handling etc)
an EJB on top of it, calling the right core objects and methods to do the job. This EJB is called by other internal modules of our ERP.
a REST layer on top of it for uses by frontend web sites.
I do not want to get the entityManager, nor the EMF (EM factory) in the EJB, because I want my middle layer to be unaware that there is a DB used under it. I could after all, later, decide to change my core implementation for a non-DB-using one.
I see only two bad solutions :
1) Add an EM parameter every time i call a method of the core layer that needs DB connection. Very ugly and goes againt what I said above.
2) In every method of core needing DB connection, I create a factory, an EM, use them, and then close them both.
I tried to cut things in the middle, having one factory per class of the Core level, and EMs are created and closed in every method. But I still have memory leaks like this :
javax.servlet.ServletException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Error in allocating a connection. Cause: In-use connections equal max-pool-size and expired max-wait-time. Cannot allocate more connections.
I guess it's because if one of my EJB methods uses 10 different objects, it creates 10 EM factories, and none of them is closed.
Example of typical use in a Core object:
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// do some stuff with em; for example persist, etc
em.flush();
em.close();
Should I go for solution 2?
Is there a way to use a single EM factory at this core level ? I seems like the JPA spec assumes you're going to use the entities at the EJB level only, which is bad in a multi layers apps.
EDIT : here is the current status after trying #Inject :
Added an empty beans.xml file in the /META-INF directory on my CORE jar.
The parent DAO class is now like this :
public class ExampleBZL {
public EntityManagerFactory emf;
#Inject public Emf emfobject;
public ExampleBZL()
{
this.emf = emfobject.emf;
}
The Emf class is very simple, and Stateless.
#Stateless
public class Emf implements EmfAbstract {
#PersistenceUnit(unitName = Setup.persistenceUnitName)
public EntityManagerFactory emf;
public Emf()
{
}
}
I must be doing something wrong, but the injection doesn't work, altough in glassfish I see "[ejb, weld, web]" in the engines list, so the CDI is loaded.
Servlet.service() for servlet Jersey Web Application threw exception
java.lang.NullPointerException
at com.blablabla.core.bizlogic.ExampleBZL.<init>(ExampleBZL.java:40)
Am I missing other annotations ?? Is it really working to do an inection in a JAR with those two small annotations (Stateless on one side, Inject on the other) ?
With JavaEE 6 you can define your core level classes as Beans and inject resources there.
Please check Context and Dependency Injection (CDI) with JavaEE 6.
What if you had two session beans? One with the injected EntityManager that can leverage JTA, and the other is your current session bean.
I am currently putting together a series on my blog using a session bean as the REST service using EclipseLink & Glass Fish v3:
Part 1 - The Database Model
Part 2 - Mapping the Database Model to JPA Entities
Part 3 - Mapping JPA Entities to XML using JAXB
Part 4 - The RESTful Service
Below is how I inject the EntityManager on my session bean that is serving as my REST service:
package org.example.customer;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.ws.rs.Path;
import org.eclipse.persistence.rest.JPASingleKeyResource;
#Stateless
#LocalBean
#Path("/customers")
public class CustomerService {
#PersistenceContext(unitName="CustomerService", type=PersistenceContextType.TRANSACTION)
EntityManager entityManager;
}
You can link your session beans using the #EJB annotation:
package org.example;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.naming.Context;
import javax.naming.InitialContext;
#Stateless
#LocalBean
#EJB(name = "someName", beanInterface = CustomerService.class)
public class OtherSessionBean {
public Customer read(long id) {
try {
Context ctx = new InitialContext();
CustomerService customerService = (CustomerService) ctx.lookup("java:comp/env/someName");
return customerService.read(id);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
I am not sure it is a good answer, but I have found this :
link of forum saying :
it appears that interaction between
JPA and CDI was considered but not
made part of the spec for some unknown
reason. If I come to know of the
reason, I shall update this thread. In
the mean while, it has been sent as a
feedback to appropriate folks. So,
this one is definitely not a bug in
GlassFish.
So, does that explain why my #Inject (of a class containing an entity manager) in a java class is not working ?
Here is the final working code, thanks to Blaise :
The father class that "receives" the connection
import com.wiztivi.apps.wsp.billing.interfaces.bin.db.NewInterface;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
#Stateless
#LocalBean
public class FatherService {
public EntityManager em;
public FatherService()
{
}
public EntityManager getGoodEm()
{
try {
Context ctx = new InitialContext();
NewInterface dp = (NewInterface) ctx.lookup("java:global/billing-ear/billing-connection/DataProvider");
em = dp.getEm();
} catch(Exception e) {
throw new RuntimeException(e);
}
return em;
}
}
The class who "provides" the connection (in a separate connection JAR, with the entities)
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
#Stateless
#LocalBean
public class DataProvider implements NewInterface
{
#PersistenceContext(unitName=Setup.persistenceUnitName, type=PersistenceContextType.TRANSACTION)
public EntityManager entityManager;
public DataProvider() {
}
#Override
public EntityManager getEm()
{
return entityManager;
}
}
Something important : You have to put #Stateless on any class of "higher level" layer" that will call the FatherService EJB (in my case, the REST classes).
The Core layer must be packaged as an EJB, and the connection too, both in an EAR
that's how the session factory should be gotten:
protected SessionFactory getSessionFactory() {
try {
return (SessionFactory) new InitialContext()
.lookup("SessionFactory");
} catch (Exception e) {
}
}
Please provide a simple solution for Tomcat6 to be able to get SessionFactory
thru simple jndi lookup in Java code.
What should be written in what file on the side of Tomcat ?
Here's a link
http://community.jboss.org/wiki/UsingJNDI-boundSessionFactorywithTomcat41
But other answers are welcome as well.
Tomcat documentation says:
Tomcat provides a read-only InitialContext, while Hibernate requires
read-write in order to manage multiple session factories. Tomcat is
apparently following the specification for unmanaged containers. If
you want to bind the session factory to a JNDI object, you'll either
have to move to a managed server (Glassfish, JBoss, etc.), or search
on the Internet for some posted work-arounds.
The recommendation from the Hibernate documentation is to just leave
out the hibernate.session_factory_name property when working with
Tomcat to not try binding to JNDI.
And Hibernate documentation says the same:
It is very useful to bind your SessionFactory to JDNI namespace. In
most cases, this is possible to use hibernate.session_factory_name
property in your configuration. But, with Tomcat you cann't use
hibernate.session_factory_name property, because Tomcat provide
read-only JNDI implementation. To use JNDI-bound SessionFactory with
Tomcat, you should write custom resource factory class for
SessionFactory and setup it Tomcat's configuration.
So you need to make custom SessionFactory like that:
package myutil.hibernate;
import java.util.Hashtable;
import java.util.Enumeration;
import javax.naming.Name;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.RefAddr;
import javax.naming.spi.ObjectFactory
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateSessionFactoryTomcatFactory implements ObjectFactory{
public Object getObjectInstance(Object obj, Name name, Context cntx, Hashtable env)
throws NamingException{
SessionFactory sessionFactory = null;
RefAddr addr = null;
try{
Enumeration addrs = ((Reference)(obj)).getAll();
while(addrs.hasMoreElements()){
addr = (RefAddr) addrs.nextElement();
if("configuration".equals((String)(addr.getType()))){
sessionFactory = (new Configuration())
.configure((String)addr.getContent()).buildSessionFactory();
}
}
}catch(Exception ex){
throw new javax.naming.NamingException(ex.getMessage());
}
return sessionFactory;
}
}