LocalContainerEntityManagerFactoryBean - java

I have gone through the spring data jpa reference documentation
to configure a datasource in spring boot,and with LocalcontainerEntityManagerFactoryBean and transactionManager..etc,but run it
with error
but I want configure a datasource of mysql,single datasource.
this is configuration class code:
#Configuration
#EnableJpaRepositories
#EnableTransactionManagement
public class DataSourceConfig {
#Bean
#ConfigurationProperties(prefix="oneslide.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendor=new HibernateJpaVendorAdapter();
vendor.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory=new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendor);
factory.setPackagesToScan("com.oneslide.multiDataSource.domain");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager=new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
I don't want user DatasourceBuilder.create.url().password().. something chain invocation like that,I just want to congiure my sql connection metadata in
application.properties with oneslide.datasource namespce.And try to use the LocalContainerEntityManagerFactory Bean,not with tutorial's way in which they
use spring.datasource.* property.
but when i run it datasource debug info is null,there it is digest of exception log:
Invocation of init method failed; nested exception is
org.hibernate.service.spi.ServiceException: Unable to create requested
service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
: Access to DialectResolutionInfo cannot be null when
'hibernate.dialect' not set
Help me thanks a lot.

I totally misunderstand spring boot,Maybe.....it autoconfigure all bean,like
LocalContainerEntityManagerfactoryBean!so to use multidatasource,I just need to configure a datasource only,Right???

Your annotation #ConfigurationProperties(prefix="oneslide.datasource") is asking Sprint to get the info from the external configuration and bind it with the bean you are annotating, i.e. the result produced by the method.
BUT the annotated bean MUST have the properties to receive the configuration values. I.e. it has to have fields and inner objects that replicate the structure of the configurations you are passing (and the setters too).
As example if your config contains something like below:
oneslide.datasource.url = some_url
oneslide.datasource.user = usr
oneslide.datasource.password = pw
oneslide.datasource.special.detail = whatever
The bean you build should have fields "url", "user" and "password" AND an object "special" with a field "detail", so that Spring can set the values. Simplifying something along Y = X.getSpecial(); Y.setDetail() (with null recognition and object creation too, I think to remember).
If you do nothing... Spring behind the scene will create a DataSourceProperties bean (that unsurprisingly contains the fields normally used to set up a datasource with the config info under "spring.datasource").
You can get hold of this bean by defining your own bean that gets it as a parameter, like below:
public <whatever> getTheD_S_Properties(DataSourceProperties myDataSourceValesFromConfig) {
...do something with the bean you got,
that contains the values from your config...
}
The most common operation, in this case is to just build the datasource yourself, with some logic beyond just assigning the values form the config.
If you need to do nothing special, then let Spring to build the datasource too.
Just sit back and enjoy ! :)

Related

Programmatically adding hibernate interceptor in JpaProperties - Spring

I'm writing a library with spring boot, and I need to programmatically insert a hibernate interceptor through it (because I can't use a .properties in a lib).
I want to avoid providing my own sessionFactory bean, I think it would be good to leave that possibility to an implementing project, also saves me from manually scanning for entities.
My dumb idea was that I could "inject" my interceptor into the JpaProperties. That didn't work at all, it ran the #PostConstruct but nothing changed. I had a feeling this wouldn't work, but I would like to understand why, and how I may get this to work.
#Autowired private JpaProperties properties;
#Autowired private MyInterceptor myInterceptor; //yep a bean
#PostConstruct public void add() {
((Map) properties.getProperties())
.put(
"hibernate.session_factory.interceptor",
myInterceptor
);
}
As this is using an #PostConstruct annotation, the addition to the JpaProperties will only occur after the EntityManagerFactoryBuilder has been created in JpaBaseConfiguration. This means that changes to the property map will not be present in the builder after this point.
To customize the JpaProperties, you should instantiate a bean which adds your configuration in, like:
#Primary
#Bean
public JpaProperties jpaProperties() {
JpaProperties properties = new JpaProperties();
properties.getProperties().put("hibernate.session_factory.interceptor", myInterceptor);
return properties;
}
This will then be injected into HibernateJpaConfiguration and used when constructing the EntityManagerFactoryBuilder.

MyBatis mapper class not registered in Spring Boot application with two datasources

We have a Spring Boot application that should access stored procedures from two different databases, DB2 and Oracle, through MyBatis mappers
We have created two DB2 context classes, e.g. for DB2
#Configuration
#MapperScan({ "...mapper.mybatis.db2" })
public class Db2Context {
#Primary
#Bean(name = "db2DataSource")
public DataSource getDataSource() { ...
#Primary
#Bean(name = "db2SqlSessionFactory")
public SqlSessionFactory getSqlSessionFactory() {...
The MyBatis beans look like
public interface Db2Mapper extends MyBatisMapper<SomeType> {
#Override
#Select(value = ...)
#Options(statementType = StatementType.CALLABLE)
#Results({...})
List<SomeType> select(Map<String, Object> parameters);
And the SqlSessionFactory beans are injected into the respective DAO classes with the appropriate qualification, e.g.
#Repository
public class Db2Dao {
#Autowired
#Qualifier("db2SqlSessionFactory")
SqlSessionFactory sqlSessionFactory;
...
try(SqlSession session= sqlSessionFactory.openSession(true);) {
Db2Mapper mapper = session.getMapper(Db2Mapper.class);
resultSet = mapper.select(parameters);
We have the identical config, mapper and DAO for Oracle as well, except that in that config the DataSource and SqlSessionFactory beans are not annotated with #Primary. This was necessary as per described in the Spring Boot reference: http://docs.spring.io/spring-boot/docs/1.2.3.RELEASE/reference/htmlsingle/#howto-two-datasources; without that the Spring Boot application startup would result in NoUniqueBeanDefinitionException
With this configuration the Spring Boot application starts up succesfully, and during the startup there are even INFO log printouts indicating that both mapper classes have been succesfully identified
INFO BeanPostProcessorChecker : Bean 'db2Mapper' of type [class org.mybatis.spring.mapper.MapperFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
INFO BeanPostProcessorChecker : Bean 'oracleMapper' of type [class org.mybatis.spring.mapper.MapperFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
However, in runtime we have a problem. First Db2Dao is executed, and with that everything goes perfectly, the DB2 stored procedure is getting executed, and the retrieved results are stored through Db2Mapper. Then comes OracleDao; however after the Oracle SP execution the following exception is received
ERROR Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; ... Type interface com....mapper.mybatis.oracle.OracleMapper is not
known to the MapperRegistry.]
We have been fighting with this issue for some while now, but could not find a resolution. Possibly the usage of #Primary might have something to do with it, but without that we are not even able to start up the application. Our researches actually seem to indicate that different library versions might even provide different behaviour: our stack is Java 1.8, Spring Boot 1.2.6, Spring 4.1.7, MyBatis 3.2.5, MyBatis-Spring 1.2.2
First of all, I would suggest not to autowire SqlSessionFactory into your DAOs at all. In fact you can get rid of DAOs completely and use your mappers in service layer as spring beans.
So you do something like this:
public interface Db2Mapper extends MyBatisMapper<SomeType> {
#Override
#Select(value = ...)
#Options(statementType = StatementType.CALLABLE)
#Results({...})
List<SomeType> select(Map<String, Object> parameters);
}
#Service
public class Db2Service{
#Autowired
private Db2Mapper db2Mapper;
//...
}
Secondly, the key to have various datasources integrated with mybatis-spring is in sqlSessionFactoryRef attribute of #MapperScan annotation. With that you can narrow down which SqlSessionFactory instance is used for witch #MapperScan. Something like this:
#Configuration
#MapperScan(value = { "...mapper.mybatis.db2" }, sqlSessionFactoryRef = "db2SqlSessionFactory")
public class Db2Context {
#Primary
#Bean(name = "db2DataSource")
public DataSource getDataSource() { ...
#Primary
#Bean(name = "db2SqlSessionFactory")
public SqlSessionFactory getSqlSessionFactory() {...
#Configuration
#MapperScan(value = { "...mapper.mybatis.other" }, sqlSessionFactoryRef = "otherSqlSessionFactory")
public class OtherContext {
#Bean(name = "otherDataSource")
public DataSource getDataSource() { ...
#Bean(name = "otherSqlSessionFactory")
public SqlSessionFactory getSqlSessionFactory() {...
Obviously you shouldn't scan same packages with these two #MapperScan annotations.

JPA without persistence.xml

I'm trying to get started with using Guice Persist and JPA, which recommends using configuration via persistence.xml. Coming from a native Hibernate background where configuration was obtained programmatically, is there a simple way to configure a JpaPersistModule without a persistence.xml file, or will a rump persistence.xml always have to exist?
If no such option exists, it might be the case where I might have to play around with PersistenceProvider (assuming the "default" parses persistence.xml somehow). Any tutorials on working with the JPA SPI?
There is no need for persistence.xml if you are using a Spring version higher than 3.1 and you have already defined your entities classes.
#Configuration
#ComponentScan(basePackages = { "com.demoJPA.model" })
#EnableTransactionManagement
public class DemoJPAConfig {
#Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("org.gjt.mm.mysql.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/cimto");
dataSource.setUser("user");
dataSource.setPassword("pass");
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws PropertyVetoException {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setJpaVendorAdapter(vendorAdapter());
em.setPersistenceUnitName("cimtoPU");
em.setJpaPropertyMap(getJpaProperties());
return em;
}
public Map<String, ?> getJpaProperties() {
return new HashMap<String, Object>();
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
public JpaVendorAdapter vendorAdapter() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
vendorAdapter.setShowSql(true);
return vendorAdapter;
}
}
Note: com.demoJPA.model package must contain your entities classes.
Assuming that you have a PersistenceProvider implementation (e.g. Hibernate), you can use the PersistenceProvider#createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) method to bootstrap an EntityManagerFactory without needing a persistence.xml.
However, it's annoying that you have to implement the PersistenceUnitInfo interface, so you are better off using Spring or Hibernate which both support bootstrapping JPA without a persistence.xml file:
this.nativeEntityManagerFactory = provider.createContainerEntityManagerFactory(
this.persistenceUnitInfo,
getJpaPropertyMap()
);
Where the PersistenceUnitInfo is implemented by the Spring-specific MutablePersistenceUnitInfo class.
Depending on what you want to achieve and in what context (ApplicationServer vs CLI, CMT transactions vs EntityTransactions), it may be possible to use JPA without a persistence.xml. I did this in a CLI Java application, where I had different databases with the same structure. For that I constructed the EntityManagerFactory manually.
PS: The config file is there to make your life easier, so if you can, just use it.

How to get Spring Data to handle multiple heterogeneous DataSources?

I've been successful using using the Accessing Data With JPA tutorial for Spring. I've gotten a CrudRepository of my own to work automatically by just configuring a specific DataSource #Bean, and the internal connections between these are managed by Spring Data (or Spring Boot, it's hard to tell which).
However, I can't figure out how to get that automated plumbing to handle a second DataSource #Bean. Injecting a second one causes the autoconfiguration classes to explode during startup.
Any thoughts as to how to do this? The searches I've done for this resulted in articles discussing multiple homogeneous DataSources for load balancing or other purposes, which is really not what I need. I have multiple databases with completely separate content that I need to pull into this app and I'd really like to avoid having to replicate all that automated configuration just because a second database entered the mix.
I'm hoping this is simple, but I'm fearful that it's an unsupported edge case in the autoconfiguration.
You can create two datasources and entitymanagers, one bean of them mark as #Primary
#Configuration
#EnableJpaRepositories(basePackages = "io.eddumelendez.springdatajpa.repository1")
public class FirstConfiguration {
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource postgresDataSource() {
return DataSourceBuilder.create().
build();
}
#Bean(name = "entityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean emf1(EntityManagerFactoryBuilder builder){
return builder
.dataSource(postgresDataSource())
.packages("io.eddumelendez.springdatajpa.domain1")
.persistenceUnit("users")
.build();
}
}
Configuration for another datasource:
#Configuration
#EnableJpaRepositories(basePackages = "io.eddumelendez.springdatajpa.repository2", entityManagerFactoryRef = "emf2")
public class SecondConfiguration {
#Bean
#ConfigurationProperties(prefix = "datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean emf2(EntityManagerFactoryBuilder builder){
return builder
.dataSource(mysqlDataSource())
.packages("io.eddumelendez.springdatajpa.domain2")
.persistenceUnit("customers")
.build();
}
}
Your application.properties should looks like this:
datasource.mysql.url=jdbc:mysql://localhost:3306/mysql_demo
datasource.mysql.username=root
datasource.mysql.password=root
datasource.postgres.url=jdbc:postgresql://localhost:5432/postgres_demo
datasource.postgres.username=postgres
datasource.postgres.password=postgres

How can I access the jdbc Connection in a JPA Converter?

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.

Categories

Resources