Atomikos + OpenJpa +DB2 XA standalone - setAutocommit(true) not allowed - java

I have a spring batch project and am using Atomikos for transaction management.
Here is my datasource configuration and atomikos configuration.
#Bean
public DataSource dataSource() {
Properties properties = new Properties();
properties.setProperty("serverName", "localhost");
properties.setProperty("databaseName", "somedb");
properties.setProperty("portNumber", "9999");
properties.setProperty("currentSchema", "sch");
properties.setProperty("user", "user");
properties.setProperty("password", "pwd");
properties.setProperty("driverType", "4");
properties.setProperty("resultSetHoldability","2");
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSourceClassName("com.ibm.db2.jcc.DB2XADataSource");
atomikosDataSourceBean.setXaProperties(properties);
atomikosDataSourceBean.setPoolSize(5);
return atomikosDataSourceBean;
}
#Bean(initMethod = "init", destroyMethod = "close")
public UserTransactionManager atomikosTransactionManager(){
UserTransactionManager atomikosTransactionManager = new UserTransactionManager();
atomikosTransactionManager.setForceShutdown(false);
return atomikosTransactionManager;
}
#Bean
public UserTransactionImp atomikosUserTransaction() throws SystemException{
UserTransactionImp atomikosUserTransaction = new UserTransactionImp();
atomikosUserTransaction.setTransactionTimeout(300);
return atomikosUserTransaction;
}
#Bean
public JtaTransactionManager transactionManager(UserTransactionManager atomikosTransactionManager, UserTransactionImp atomikosUserTransaction) throws SystemException{
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setTransactionManager(atomikosTransactionManager);
jtaTransactionManager.setUserTransaction(atomikosUserTransaction);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setJtaDataSource(dataSource);
em.setPackagesToScan("org.company.entity");
em.setJpaVendorAdapter(new OpenJpaVendorAdapter());
em.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
em.setSharedCacheMode(SharedCacheMode.ALL);
em.setJpaProperties(additionalProperties());
return em;
}
final Properties additionalProperties() {
final Properties openJpaProperties = new Properties();
openJpaProperties.setProperty("javax.persistence.transactionType","jta");
openJpaProperties.setProperty("openjpa.TransactionMode", "local");
openJpaProperties.setProperty("openjpa.ConnectionFactoryMode", "local");
openJpaProperties.setProperty("openjpa.MetaDataRepository", "Preload=true");
openJpaProperties.setProperty("openjpa.Compatibility", "QuotedNumbersInQueries=true");
openJpaProperties .setProperty("openjpa.ConnectionFactoryProperties", "PrettyPrint=true, PrettyPrintLineLength=80, PrintParameters=true");
return openJpaProperties;
}
When i call spring repository to update the database it throws me an error saying Invalid operation: setAutoCommit(true) is not allowed during Global Transaction. See stacktrace below. Any thoughts on this will be very helpful.
com.ibm.db2.jcc.am.SqlException: [jcc][t4][10126][10304][3.62.56] Invalid operation: setAutoCommit(true) is not allowed during Global Transaction. ERRORCODE=-4201, SQLSTATE=2D521
at com.ibm.db2.jcc.am.fd.a(fd.java:679)
at com.ibm.db2.jcc.am.fd.a(fd.java:60)
at com.ibm.db2.jcc.am.fd.a(fd.java:120)
at com.ibm.db2.jcc.am.jb.setAutoCommit(jb.java:960)
at com.ibm.db2.jcc.am.df.setAutoCommit(df.java:158)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:60)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
at java.lang.reflect.Method.invoke(Method.java:611)
at com.atomikos.jdbc.AtomikosConnectionProxy.invoke(AtomikosConnectionProxy.java:161)
at $Proxy38.setAutoCommit(Unknown Source)
at org.apache.openjpa.lib.jdbc.DelegatingConnection.setAutoCommit(DelegatingConnection.java:167)

I was able to fix it. The issue here is we need to tell openJpa to participate in Atomikos transactions. Here is a updated version of the additionalProperties() method:
#Bean
#DependsOn("transactionManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter,
TransactionManager transactionManager) {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setJtaDataSource(dataSource);
em.setPackagesToScan("org.company.entity");
em.setJpaVendorAdapter(jpaVendorAdapter);
em.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());
em.setSharedCacheMode(SharedCacheMode.ALL);
em.setJpaProperties(additionalProperties(dataSource, transactionManager));
return em;
}
final Properties additionalProperties(DataSource dataSource, TransactionManager transactionManager) {
final Properties openJpaProperties = new Properties();
openJpaProperties.put("javax.persistence.transactionType", "JTA");
openJpaProperties.put("openjpa.TransactionMode", "managed");
openJpaProperties.put("openjpa.ConnectionFactoryMode", "managed");
openJpaProperties.put("openjpa.ConnectionFactory", dataSource);
openJpaProperties.put("openjpa.ManagedRuntime", "invocation(TransactionManagerMethod=com.atomikos.icatch.jta.TransactionManagerImp.getTransactionManager)");
openJpaProperties.put("openjpa.MetaDataRepository", "Preload=true");
openJpaProperties.put("openjpa.Compatibility", "QuotedNumbersInQueries=true");
openJpaProperties.put("openjpa.ConnectionFactoryProperties", "PrettyPrint=true, PrettyPrintLineLength=80, PrintParameters=true");
return openJpaProperties;
}

Related

Multiple database connection reusing entity mapping

We have a case where we connect to different databases environments, but the tables in each of the environment are the same.
Is there any way I can reuse the entity class for each of the environment?
I am using separate config class for each of the environment. Below is the config for one of the environment, similarly I have 4 others. The "packages" deduce which environment to connect to.
codejava
#Configuration
#EnableJpaRepositories(basePackages = {"packages"},
entityManagerFactoryRef = "OneEntityManager",
transactionManagerRef = "OneTransactionManager")
public class DevDataSourceConfig {
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean OneEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(OneDataSource());
em.setPackagesToScan(String[]{"packages"});
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean
public DataSource OneDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverclass);
dataSource.setUrl(url);
dataSource.setUsername(uName);
dataSource.setPassword(dbPass);
return dataSource;
}
#Primary
#Bean
public PlatformTransactionManager OneTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(OneEntityManager().getObject());
return transactionManager;
}
}

org.hibernate.service.UnknownUnwrapTypeException: Cannot unwrap to requested type [javax.sql.DataSource]

I am getting this error while running my application : org.hibernate.service.UnknownUnwrapTypeException: Cannot unwrap to requested type [javax.sql.DataSource]
My configuration class :
#Configuration
#PropertySource("classpath:META-INF/spring/jdbc.properties")
public class HibernateConfig {
#Autowired
private Environment env;
#Bean(name="dataSource")
public DataSource getDataSource() throws PropertyVetoException{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(env.getProperty("db.driverClass"));
dataSource.setJdbcUrl(env.getProperty("db.jdbcUrl"));
dataSource.setUser(env.getProperty("db.user"));
dataSource.setPassword(env.getProperty("db.password"));
dataSource.setMaxPoolSize(50);
dataSource.setMinPoolSize(5);
dataSource.setMaxConnectionAge(1800);
dataSource.setMaxIdleTime(1800);
dataSource.setAutoCommitOnClose(false);
dataSource.setInitialPoolSize(5);
return dataSource;
}
#Bean(name="sessionFactory")
#Scope("singleton")
public LocalSessionFactoryBean getSessionFactory(DataSource dataSource){
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("db.dialect"));
hibernateProperties.setProperty("hibernate.jdbc.batch_size", "0");
hibernateProperties.setProperty("c3p0.acquire_increment", "1");
localSessionFactoryBean.setHibernateProperties(hibernateProperties);
return localSessionFactoryBean;
}
#Bean(name="transactionManager")
public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory){
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory);
return transactionManager;
}
}
I ran actually into this thread which talks about the same issue. However I am wondering if it is a sort of bug in hibernate5 that has not been fixed yet or does it relate to something else. Thank you in advance.

Hibernate cannot alter tables during deploy to Heroku

I am trying to deploy my web app to Heroku. I have configured Hibernates (as the persistence provider) to do the following:
create schema based on entities;
fill data from sql script;
Here is my configuration:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(getDataSource());
entityManagerFactoryBean.setPackagesToScan("model");
entityManagerFactoryBean.setPersistenceProvider(new HibernatePersistenceProvider());
entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
return entityManagerFactoryBean;
}
#Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(getDataSource());
return transactionManager;
}
#Bean
public DataSource getDataSource() {
String dbUrl = System.getenv("JDBC_DATABASE_URL");
String username = System.getenv("JDBC_DATABASE_USERNAME");
String password = System.getenv("JDBC_DATABASE_PASSWORD");
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setUrl(dbUrl);
basicDataSource.setUsername(username);
basicDataSource.setPassword(password);
return basicDataSource;
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
properties.setProperty("hibernate.hbm2ddl.import_files", "test_data.sql");
return properties;
}
However, it seems that Hibernate cannot alter existing Heroku schema:
2016-07-16T15:04:20.978399+00:00 app[web.1]: 2016-07-16 15:04:20 ERROR SchemaExport:483 - HHH000389: Unsuccessful: alter table fueling drop constraint fuel_type
2016-07-16T15:04:20.975268+00:00 app[web.1]: 2016-07-16 15:04:20 ERROR SchemaExport:484 - ERROR: relation "customer" does not exist
2016-07-16T15:04:20.985169+00:00 app[web.1]: 2016-07-16 15:04:20 ERROR SchemaExport:484 - ERROR: relation "fueling" does not exist
Could you please advise what I am doing wrong?

Jhipster Multi-tenancy with Hibernate Second Level Caching

I've been attempting to turn my JHipster generated application into a multi-tenancy app using this - http://jannatconsulting.com/blog/?p=41 blog post as a base.
I've run into a problem with second level caching. Spring boot appears to correctly detect and set up:
DatabaseConfiguration.java
#Configuration
#EnableConfigurationProperties(JpaProperties.class)
#EnableJpaRepositories(
entityManagerFactoryRef = "masterEntityManager",
transactionManagerRef = "masterTransactionManager",
basePackages = {"com.quadrimular.nts.helium.repository.master"})
#EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
#EnableTransactionManagement
#EnableElasticsearchRepositories("com.quadrimular.nts.helium.repository.search")
public class DatabaseConfiguration {
#Inject
private Environment env;
#Autowired(required = false)
private MetricRegistry metricRegistry;
#Inject
private DataSourceProperties datasourceProperties;
#Inject
private JHipsterProperties jhipsterProperties;
#Inject
private JpaProperties jpaProperties;
#Inject
private DataSource dataSource;
#Bean(destroyMethod = "close")
#ConditionalOnExpression("#{!environment.acceptsProfiles('cloud') && !environment.acceptsProfiles('heroku')}")
public DataSource dataSource(DataSourceProperties dataSourceProperties, JHipsterProperties jHipsterProperties) {
log.debug("Configuring Master Datasource");
if (dataSourceProperties.getUrl() == null) {
log.error("Your database connection pool configuration is incorrect! The application" +
" cannot start. Please check your Spring profile, current profiles are: {}",
Arrays.toString(env.getActiveProfiles()));
throw new ApplicationContextException("Database connection pool is not configured correctly");
}
HikariConfig config = new HikariConfig();
config.setDataSourceClassName(dataSourceProperties.getDriverClassName());
config.addDataSourceProperty("url", dataSourceProperties.getUrl());
if (dataSourceProperties.getUsername() != null) {
config.addDataSourceProperty("user", dataSourceProperties.getUsername());
} else {
config.addDataSourceProperty("user", ""); // HikariCP doesn't allow null user
}
if (dataSourceProperties.getPassword() != null) {
config.addDataSourceProperty("password", dataSourceProperties.getPassword());
} else {
config.addDataSourceProperty("password", ""); // HikariCP doesn't allow null password
}
//MySQL optimizations, see https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
if ("com.mysql.jdbc.jdbc2.optional.MysqlDataSource".equals(dataSourceProperties.getDriverClassName())) {
config.addDataSourceProperty("cachePrepStmts", jHipsterProperties.getDatasource().isCachePrepStmts());
config.addDataSourceProperty("prepStmtCacheSize", jHipsterProperties.getDatasource().getPrepStmtCacheSize());
config.addDataSourceProperty("prepStmtCacheSqlLimit", jHipsterProperties.getDatasource().getPrepStmtCacheSqlLimit());
}
if (metricRegistry != null) {
config.setMetricRegistry(metricRegistry);
}
return new HikariDataSource(config);
}
#Bean(name = "masterEntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource(datasourceProperties, jhipsterProperties));
em.setPackagesToScan(new String[]{"com.quadrimular.nts.helium.domain.master"});
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalJpaProperties());
em.setPersistenceUnitName("master");
return em;
}
private Properties additionalJpaProperties() {
Properties properties = new Properties();
for (Map.Entry<String, String> entry : jpaProperties.getHibernateProperties(dataSource).entrySet()) {
properties.setProperty(entry.getKey(), entry.getValue());
}
return properties;
}
#Bean(name = "masterTransactionManager")
public JpaTransactionManager transactionManager(EntityManagerFactory masterEntityManager){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(masterEntityManager);
return transactionManager;
}
}
When spring tries to configure:
MultiTenancyJPAConfiguration.java
#Configuration
#EnableConfigurationProperties(JpaProperties.class)
#EnableJpaRepositories(
entityManagerFactoryRef = "tenantEntityManager",
transactionManagerRef = "tenantTransactionManager",
basePackages = {"com.quadrimular.nts.helium.repository.tenant"})
#EnableTransactionManagement
public class MultiTenancyJpaConfiguration {
#Bean(name = "tenantEntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider connectionProvider,
CurrentTenantIdentifierResolver tenantResolver) {
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(dataSource);
emfBean.setPackagesToScan("com.quadrimular.nts.helium.domain.tenant");
emfBean.setJpaVendorAdapter(jpaVendorAdapter());
Map<String, Object> properties = new HashMap<>();
properties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver);
properties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
emfBean.setJpaPropertyMap(properties);
return emfBean;
}
#Bean(name = "tenantTransactionManager")
public JpaTransactionManager transactionManager(EntityManagerFactory tenantEntityManager){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(tenantEntityManager);
return transactionManager;
}
}
I'm getting this in my trace:
Caused by: org.hibernate.cache.NoCacheRegionFactoryAvailableException: Second-level cache is used in the application, but property hibernate.cache.region.factory_class is not given; please either disable second level cache or set correct region factory using the hibernate.cache.region.factory_class setting and make sure the second level cache provider (hibernate-infinispan, e.g.) is available on the classpath.
I have all the required properties defined in my application-dev.yml
hibernate.cache.use_second_level_cache: true
hibernate.cache.use_query_cache: false
hibernate.generate_statistics: true
hibernate.cache.region.factory_class: org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
It appears that it's being read and used correctly by spring boot for my DatabaseConfiguration.java. I can't work out why it's not detecting the property file. If I try to disable the cache by setting:
hibernate.cache.use_second_level_cache: false
DatabaseConfiguration.java detects and behaves accordingly however MultiTenancyJPAConfiguration.java still throws the same exception.
Am I missing something obvious?
The answer is to actually set the jpa property values on the entity manager. I'm not sure how I overlooked this; I thought that somehow they were already set.
Firstly I injected the main datasource and the jpa properties object provided by spring boot if I'm not mistaken.
MultiTenancyJPAConfiguration.java
#Inject
private JpaProperties jpaProperties;
#Inject
private DataSource dataSource;
I then set the values using the same method used in DatabaseConfiguration.java
MultiTenancyJPAConfiguration.java
#Bean(name = "tenantEntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider connectionProvider,
CurrentTenantIdentifierResolver tenantResolver) {
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(dataSource);
emfBean.setPackagesToScan("com.quadrimular.nts.helium.domain.tenant");
emfBean.setJpaVendorAdapter(jpaVendorAdapter());
Map<String, Object> properties = new HashMap<>();
properties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
properties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver);
properties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
emfBean.setJpaPropertyMap(properties);
emfBean.setJpaProperties(additionalJpaProperties());
return emfBean;
}
private Properties additionalJpaProperties() {
Properties properties = new Properties();
for (Map.Entry<String, String> entry : jpaProperties.getHibernateProperties(dataSource).entrySet()) {
properties.setProperty(entry.getKey(), entry.getValue());
}
return properties;
}
Using the method additionalJpaProperties() to get all the hibernate jpa properties for my main datasource. I then set the hibernate property map properties after the hard coded ones. Clearly not the cleanest solution I plan to set all jpa values from the .yml file.

Set hibernate.ddl-auto in springboot programmatically

I use springboot in a non web application and data jpa. I use the default configuration except for datasource:
private static final String dataSourceUrl = "jdbc:h2:./Database;DB_CLOSE_ON_EXIT=FALSE";
#Bean
public DataSource dataSource() {
return DataSourceBuilder.create().url(dataSourceUrl).username("user").password("pwd").build();
}
How can I set the spring.jpa.hibernate.ddl-auto property also programmatically?
Adding the following bean seems to do the job (thanks to Jens' comment):
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan(new String[] { "packages.to.scan" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.setProperty("hibernate.hbm2ddl.auto", "update");
em.setJpaProperties(properties);
return em;
}

Categories

Resources