Maybe what I´m going to ask it´s a silly question, what I wan to know if it is possible in a Spring MVC configuration has two entityManagerFactory. I will explain why.
I have one LocalContainerEntityManagerFactoryBean where I configure a hibernate.tenant_identifier_resolver which I use to determine the tenant by LDAP using the session of the user, and then use one database Schema or another, then I use "multi_tenant_connection_provider" to create the database connection for that schema.
Now my application has a Scheduler that needs access to all Schemas and get some information from all databases. So in order to do not touch the entityManagerFactory already configure, what I was thinking was to create a new one with my own implementation of "hibernate.tenant_identifier_resolver" to control which schema I want instead LDAP, before create the database connection by "multi_tenant_connection_provider".
the problem looks like Spring do not allow me configure two entityManagerFactory.
Can you give me any advice about how to achieve what I want?
Regards!
Yes, it is possible to use multiple EntityManagers.
In my project I use the annotation configuration, where I have:
#Configuration
#EnableTransactionManagement
public class AppConfig {
#Bean
public SessionFactory smartDataSessionFactory() {
return new LocalSessionFactoryBuilder(smartDataDatasource())
.scanPackages("...)
.addProperties(smartDataHibernateProperties())
.buildSessionFactory();
}
#Bean
public SessionFactory analysisSessionFactory() {
return new LocalSessionFactoryBuilder(analysisDatasource())
.scanPackages("...)
.addProperties(analysisHibernateProperties())
.buildSessionFactory();
}
...
}
When referencing the entityManagers, be sure to use the Qualifier annotation.
Also note that each SessionFactory will use it's own TransactionFactory
#Repository
#Transactional(value = "analysisTransactionManager")
public class ToURemunerationDaoImpl implements ToURemunerationDao {
private SessionFactory analysisSessionFactory;
private SessionFactory smartDataSessionFactory;
#Autowired
#Qualifier("analysisSessionFactory")
public void setAnalysisSessionFactory(SessionFactory sessionFactory) {
this.analysisSessionFactory = sessionFactory;
}
#Autowired
#Qualifier("smartDataSessionFactory")
public void setSmartDataSessionFactory(SessionFactory sessionFactory) {
this.smartDataSessionFactory = sessionFactory;
}
...
}
Finally I found the solution. The issue was becuase I´m using Spring data, and my repositories did not which EntityManagerFactory use. As soon as I specify which one to use during the scan everything works like a charm.
<repositories base-package="com.*.*.repository**" entity-manager-factory-ref="entityManagerFactory"/>
Related
I'm using Spring and Hibernate with an automatically generated database (for that I have set "hibernate.hbm2ddl.auto" to "update" in the JPA configuration properties).
I also have a class annotated #Configuration with a #PostConstruct method that is called on application startup after the database has been created or updated. This is where I setup the database with some default data if it's empty (first launch).
I would like to execute some custom native SQL queries at this moment. These queries won't return anything, they're just configuration stuff (like creating additional indexes or extensions).
Currently I'm stuck on creating a SessionFactory in order to create a new Hibernate Session. I've tried auto wiring it, but it doesn't work :
#Autowired
SessionFactory sessionFactory;
Gives me: Field sessionFactory in ... required a bean of type 'org.hibernate.SessionFactory' that could not be found.
I understand that I probably need to configure it elsewhere, but I don't know where. Several answers on SO use an xml configuration file, but I'm not using any configuration file so I can't do it that way.
Is there a way Spring can create the SessionFactory with the appropriate configuration ?
You don't even need to access SessionFactory. Please just put your scripts into a file src/main/resources/scripts/myscript.sql. You can then do the following with Spring:
#Component
public class Startup {
#Autowired
private DataSource dataSource;
#PostConstruct
public void runNativeSql() {
ClassPathResource resource = new ClassPathResource("scripts/myscript.sql");
try(Connection connection = dataSource.getConnection()) {
ScriptUtils.executeSqlScript(connection, resource);
} catch (SQLException | ScriptException e) {
//LOG
}
}
}
You can autowire the JPA EntityManager as:
#PersistenceContext
EntityManager entityManager;
If you really need a Hibernate Session and are using using JPA 2.1, the Session can be obtained from the EntityManager as:
entityManager.unwrap(Session.class);
I'm using Oracle Spatial, and I have a table with an SDO_GEOMETRY field.
The table is mapped to a JPA entity. I want to have the SDO_GEOMETRY field mapped to a java oracle.spatial.geometry.JGeometry type.
I figured I should use a JPA Converter and to convert to and from java.sql.Struct (or maybe oracle.sql.STRUCT).
The problem is the JGeometry method that converts to Struct, JGeometry.storeJS(Connection conn, JGeometry geom), wants the jdbc connection as a parameter.
The spring EntityManagerFactory is configured with the persistence unit name, the persistence unit contains the data source jndi name, and the data source is defined in tomcat, as a connection pool.
Any idea on how I can get the Connection in the converter ?
This what I want to achieve:
#Converter(autoApply = true)
public class GeometryConverter implements AttributeConverter<JGeometry, Struct> {
#Override
public Struct convertToDatabaseColumn(JGeometry geometry) {
// How to get this connection ?
return JGeometry.storeJS(connection, geometry);
}
#Override
public JGeometry convertToEntityAttribute(Struct struct) {
try {
return JGeometry.loadJS(struct);
} catch (SQLException e) {
throw new RuntimeException("Failed to convert geometry", e);
}
}
}
I am using Spring 4, spring-data-jpa 1.6, Hibernate 4, Tomcat 8, Oracle 12c.
Updated with more info:
Spring configuration:
#Configuration
#EnableJpaRepositories("com.package.repository")
#EnableTransactionManagement
#ComponentScan("com.package")
public class SpringConfig {
#Bean(name = "entityManagerFactory", destroyMethod = "destroy")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setPersistenceUnitName("persistence-unit");
return emf;
}
#Bean(name = "transactionManager")
public JpaTransactionManager getTransactionManager() {
return new JpaTransactionManager();
}
}
If you use spring, and you need to use both JPA and JDBC, you should :
construct a datasource bean and make connection pooling there (or get if from jndi(*))
inject that datasource in one on the spring helpers for building the EntityManagerFactory (such as LocalContainerEntityManagerFactoryBean)
inject that datasource in any bean where you want to do direct JDBC
That way you can use JPA for your normal DAO, and still have access to JDBC in special parts - without a too strong dependance of the internals of your JPA provider.
EDIT:
(*) If your datasource is defined by a jndi name, all is fine. Expose it as a bean (ref)
If using Spring's XML schema based configuration, setup in the Spring context like this:
<xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.2.xsd">
...
<jee:jndi-lookup id="dbDataSource"
jndi-name="jdbc/DatabaseName"
expected-type="javax.sql.DataSource" />
Alternatively, setup using simple bean configuration like this:
<bean id="dbDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/DatabaseName"/>
</bean>
As you are using a JpaTransactionManager, there will not be any problem because as specified in spring javadoc This transaction manager also supports direct DataSource access within a transaction (i.e. plain JDBC code working with the same DataSource). This allows for mixing services which access JPA and services which use plain JDBC (without being aware of JPA)! provided you get your Connection through DataSourceUtils.getConnection(javax.sql.DataSource)
EDIT2 :
Ok now the only problem is how to access a singleton bean from a non bean object. A simple way to solve it is to create a holder singleton bean with a static method.
#Bean
public class DataSourceHolder implements InitializingBean {
private DataSource dataSource;
private static DataSourceHolder instance;
public static DataSource getDataSource() {
return instance.dataSource;
}
#Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
#Override
public void afterPropertiesSet() throws Exception {
DataSourceHolder.instance = this;
}
}
Then in any object, be it a bean or not, you can use
DataSource ds = DataSourceHolder.getDataSource();
Connection con = DataSourceUtils.getConnection(ds);
That would be tricky. Purely from the jpa api. You will have to dig into the specific provider implementation and get hold of the DataSource object or the PersistenceUnitInfo object.
From here you can get hold of the Connection object.
Now depending on which environment you are working. If you are on an JavaEE environment, and you inject EntityManager or its EntityManagerFactory, there is no guarantee that the return instance is an instance of the provider own implementation as this may just be a proxy that implements the interface, and hence no relation to the provider's own implementation.
On JSE environment, since you are the one creating the EntityManagerFactory from Persistence.createEntityManagerFactory(), you could tweak the provider in order to get the Connection.
I'm trying to get Spring Batch 2.2 working with JavaConfig.
Nowadays they have a #EnableBatchProcessing annotation that sets up a lot of things.
Default that annotation uses a datasource for its job data, but we don't want to save this data and don't want to create the table for it. The documentation says something about customizing but I have not been able to get it working:
The user has to provide a DataSource as a bean in the context, or else implement BatchConfigurer in the configuration class itself, e.g.:
public class AppConfig extends DefaultBatchConfigurer {
In our older version we've been able to use MapJobRepositoryFactoryBean class so it keeps all its data in memory. Is there anyway to use the full JavaConfig way and not define a DataSource? I've not been able to get it working.
Even if I define two data sources (one HSQL in-memory that never gets used) and our real Oracle datasource it does not work because it finds two data sources instead of one.
Anyone have an idea how to get this working? Or is the only solution going back to configuring this in the XML way?
Assuming that no other artifacts require a DataSource, you can use java config to create a context without a DataSource. To do that, your configuration will need to extend DefaultBatchConfigurer as you point out. In there, you'll override two methods, createJobRepository() and setDataSource(). Below is an example context (it doesn't define a job or steps, but it bootstraps all the related beans correctly).
#Configuration
#EnableBatchProcessing
public static class BatchConfiguration extends DefaultBatchConfigurer {
#Override
protected JobRepository createJobRepository() throws Exception {
MapJobRepositoryFactoryBean factory =
new MapJobRepositoryFactoryBean();
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
#Override
#Autowired
public void setDataSource(DataSource dataSource) {
if(dataSource != null) {
super.setDataSource(dataSource);
}
}
#Bean
public DataSource dataSource() {
return null;
}
}
I do think that simplifying this would be a useful feature and have added it to Jira. You can track it's progress here: https://jira.springsource.org/browse/BATCH-2048
Just define a dataSource() method in your BatchConfig Class
Here is how
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(driverUrl);
dataSource.setUsername(driverUsername);
dataSource.setPassword(driverPassword);
return dataSource;
}
This will automatically be invoked while setting up the TransactionManager
Let's say I have the following dependencies:
#Configuration
public class MyCfg {
// ...
#Bean
public Session session() {
return sessionFactory().getCurrentSession();
}
}
#Repository
#Transactional
public class MyRepo {
#Autowired
private Session session;
}
sessionFactory() is set up properly. If I inject SessionFactory instead of Session, it works just fine. However, if try and inject Session, it dies with an exception on container bootstrap because there is no session bound to thread.
Since the repository is #Transactional, I know that at run time there will be a session. How can I make it work, so that it injects AOP-initialized Session at run time, but does not try and resolve it when the repo is instantiated?
I would take a look at this bit of Spring documentation regarding bean scopes. Near the bottom they show how to use the #Scope annotation, which you will want to apply to your session() method in MyCfg. It sounds like you would want to use the 'request' value, which will create a new instance of this bean for each HTTP request coming in.
I will also suggest taking a look at the <aop:scoped-proxy/> element for configuration. It is mentioned a couple times in the documentation, and may be useful for what you are trying to do.
This approach will get you into a lot of trouble. Instead of injecting a Session, which you now automatically scopes as a singleton, you should inject the SessionFactory instead. Instances of Session acquired within a #Transactional annotated method will adhere to those transaction rules, eg:
#Transactional(readonly=true)
public List<Person> getPersons() {
Session session = sessionFactory.getCurrentSession();
//find those darn people.
}
#Autowired
private SessionFactory sessionFactory;
I have to do some database stuff in my repository' #PostConstruct:
#Repository
public class SomeRepositoryHibernate implements SomeRepository {
private SessionFactory sessionFactory;
#Autowired
public SomeRepositoryHibernate(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
...
#PostConstruct
public void doSomestuffWithDb() {
...
}
}
but it fails with:
org.hibernate.HibernateException: No Hibernate Session bound to thread, and
configuration does not allow creation of non-transactional one here
is there any easy solution for that?
Thanks!
You don't have a running transaction in #PostConstruct
you can't use #Transactional there (except for mode="aspectj"), so spring can't start the transaction
a transaction is required for hibernate to mutate data (insert/update/delete)
So, you would have to create your session from the session factory (via .openSession()) there and start a transaction manually.
assuming you are using hibernate combining with spring and you have configured your sessionFactory and transaction manager correctly in your spring config file.
Then the root cause is that when your doSomestuffWithDb() method is invoked the transaction prepare work has not been finished by spring. The #postconstruct can only ensure the method is called after the bean is created, it can not ensure the container is ready for everything- here, I mean the transaction related stuff- at the moment.
There is a detailed discussion in spring forum.
http://forum.springsource.org/showthread.php?58337-No-transaction-in-transactional-service-called-from-PostConstruct
Also, the author submitted his solution to jira at
https://jira.springsource.org/browse/SPR-5966?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel#issue-tabs