Spring Transactional annotation vs javax transactional - java

I am working with Spring 4.3.x and Hibernate 5.2.x. Since hibernate 5 JPA is standard way to user ORM with spring, I created a config to use Hibernate with JPA.
My spring configuration is,
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.zaxxer.hikari.HikariDataSource;
#Configuration
#ComponentScan(basePackages = { "com.example.reports" })
#EnableTransactionManagement
#PropertySource(value = { "classpath:application.properties" })
public class SuperUserReportSpringConfiguration {
#Autowired
Environment environment;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendeorAdapter) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setJpaVendorAdapter(jpaVendeorAdapter);
entityManagerFactory.setPackagesToScan("com.example.reports.domain");
entityManagerFactory.setPersistenceUnitName("onlineDB");
return entityManagerFactory;
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(false);
adapter.setDatabasePlatform("org.hibernate.dialect.DB2400Dialect");
return adapter;
}
#Bean
DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setPoolName("hikariCP");
ds.setDriverClassName(this.environment.getProperty("jdbc.as400.db2.db.driver.classname"));
ds.setJdbcUrl(this.environment.getProperty("jdbc.as400.db2.db.url"));
ds.setUsername(this.environment.getProperty("jdbc.as400.db2.db.username"));
ds.setPassword(this.environment.getProperty("jdbc.as400.db2.db.password"));
ds.setMaximumPoolSize(10);
return ds;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager(emf);
return transactionManager;
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
}
And I try to use #Transactional from javax.transaction.Transactional and org.springframework.transaction.annotation.Transactional
In both the case, transaction manager is not working and I am getting error when trying to get child element from User entity,
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.reports.domain.User.userInetAddrs, could not initialize proxy - no Session
I am not sure if I am missing any configuration.
Hibernate entity,
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<UserInetAddr> userInetAddrs = new HashSet<UserInetAddr>(0);
Service class with #Transactional
User user = superUserReportDao.getUserSecurityRoles(id);
user.getUserInetAddrs().forEach(inetAddress -> {
});
And DAO Method with #Transactional
public User getUser(int userSeqId) {
return entityManager.find(User.class, userSeqId);
}
DAO Class transaction,
#Repository("superUserReportDao")
#Slf4j
#Transactional
public class SuperUserReportDaoImpl implements SuperUserReportDao {
#PersistenceContext(unitName="onlineDB")
private EntityManager onlineEntityManager;
And Service Class,
#Service("superUserReportService")
#Slf4j
public class SuperUserReportServiceImpl implements SuperUserReportService {
#Resource(name = "superUserReportDao")
private SuperUserReportDao superUserReportDao;
public void generateSuperUserReport() throws SQLException {
// -- generate Customer Super user report
generateSuperUserEmail(UserType.CUSTOMER);
}
#Transactional
private void generateSuperUserEmail(UserType userType) throws SQLException {
...
And my main class,
#Slf4j
#Component
public class SuperUserReportMain {
#Resource(name = "superUserReportService")
SuperUserReportService superUserReportService;
/**
* #param args
* #throws SQLException
*/
public static void main(String[] args) throws SQLException {
ApplicationContext context = new AnnotationConfigApplicationContext(SuperUserReportSpringConfiguration.class);
SuperUserReportMain reportMain = context.getBean(SuperUserReportMain.class);
reportMain.generateSuperUserReport();
((AnnotationConfigApplicationContext)context).close();
}
public void generateSuperUserReport() throws SQLException {
superUserReportService.generateSuperUserReport();
}
}

Related

EntityManager doesn't translate camel case to snake case

I'm using SpringBoot 2.7.0 and trying to set an entity manager cause I have 2 databases.
But the entityManager that I instantiated doesn't work like default one.
It doesn't translate camel case to snake case for the properties of entity when it creates tables.
Even it doesn't follow the settings in application.yml. for example, spring.jpa.show-sql.
I configured it with below code.
#Slf4j
#RequiredArgsConstructor
#EnableJpaAuditing
#EnableJpaRepositories(basePackages = "com.xxx.yyy", entityManagerFactoryRef = "businessEntityManagerFactory", transactionManagerRef = "businessTransactionManager")
#EntityScan(basePackages = "com.xxx.yyy")
#Configuration
public class JpaConfiguration {
#Bean
public LocalContainerEntityManagerFactoryBean businessEntityManagerFactory(EntityManagerFactoryBuilder builder,
DataSource businessDataSource) {
return builder
.dataSource(businessDataSource)
.packages("com.xxx.yyy")
.build();
}
#Bean
public PlatformTransactionManager businessTransactionManager(LocalContainerEntityManagerFactoryBean businessEntityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(businessEntityManagerFactory.getObject()));
}
}
Does anyone know how I can instantiate an entity manager with same settings like spring boot default one?
Spring's convention-over-configuration does not work well with multiple database configurations.
Particuallary the configuration has to be autowired "manually". That's the reason springt.jpa.show-sql is not considered.
I will post an example on how to configure a database "manually".
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.zaxxer.hikari.HikariDataSource;
#Configuration
#EnableTransactionManagement
#EnableAutoConfiguration(exclude = {JdbcRepositoriesAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class, DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
#EnableJpaRepositories(basePackageClasses = {ExampleXyzRepository.class}, transactionManagerRef = DatabaseConfig.SPRING_TRANSACTION_MANAGER)
public class DatabaseConfig {
public static final String SPRING_TRANSACTION_MANAGER = "springTransactionManager";
private static LocalContainerEntityManagerFactoryBean createEntityManagerFactory(
final ObjectProvider<PersistenceUnitManager> persistenceUnitManagerProvider, final JpaProperties jpaProperties,
final HibernateProperties hibernateProperties, final DataSource dataSource, final Class<?> exampleEntityClass, final String persistenceUnit) {
return createEntityManagerFactoryBuilder(persistenceUnitManagerProvider.getIfAvailable(), jpaProperties, hibernateProperties)
.dataSource(dataSource)
.packages(exampleEntityClass)
.persistenceUnit(persistenceUnit)
.build();
}
private static EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(
final PersistenceUnitManager persistenceUnitManager, final JpaProperties jpaProperties, final HibernateProperties hibernateProperties) {
final JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
final Map<String, Object> expandedProperties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
return new EntityManagerFactoryBuilder(jpaVendorAdapter, expandedProperties, persistenceUnitManager);
}
private static JpaVendorAdapter createJpaVendorAdapter(final JpaProperties jpaProperties) {
final AbstractJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(jpaProperties.isShowSql());
adapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
adapter.setGenerateDdl(jpaProperties.isGenerateDdl());
if (jpaProperties.getDatabase() != null) {
adapter.setDatabase(jpaProperties.getDatabase());
}
return adapter;
}
private static HikariDataSource initializeDataSource(final DataSourceProperties dataSourceProperties) {
return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean
#ConfigurationProperties("spring.datasource")
public DataSourceProperties springDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("spring.datasource.hikari")
public DataSource springDataSource(#Qualifier("springDataSourceProperties") final DataSourceProperties springDataSourceProperties) {
return initializeDataSource(springDataSourceProperties);
}
#Bean
#ConfigurationProperties("spring.jpa")
public JpaProperties springJpaProperties() {
return new JpaProperties();
}
#Bean
#ConfigurationProperties("spring.jpa.hibernate")
public HibernateProperties springHibernateProperties() {
return new HibernateProperties();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
final ObjectProvider<PersistenceUnitManager> persistenceUnitManagerProvider,
#Qualifier("springJpaProperties") final JpaProperties springJpaProperties,
#Qualifier("springHibernateProperties") final HibernateProperties springHibernateProperties,
#Qualifier("springDataSource") final DataSource springDataSource) {
return createEntityManagerFactory(persistenceUnitManagerProvider, springJpaProperties, springHibernateProperties, springDataSource, ExampleXyzEntity.class, "default-persistence-unit");
}
#Bean(SPRING_TRANSACTION_MANAGER)
public JpaTransactionManager transactionManager(#Qualifier("entityManagerFactory") final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
See how the configuration of DataSourceProperties, HibernateProperties, DataSource and JpaProperties are bound.
The second database is configured similar.
But:
use a different configuration prefix and not spring.jpa and spring.datasource - obviously
define the transactionManager in the org.springframework.transaction.annotation.Transactional while accessing the second database, e.g. in the CrudRepository
Sven's answer might be right but I didn't try it because I saw this after I found the solution and it looked requiring many changes my codes.
I figured this out with below codes
#Bean
public LocalContainerEntityManagerFactoryBean businessEntityManagerFactory(DataSource businessDataSource) {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(businessDataSource);
emf.setPackagesToScan("com.xxx.yyy");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(Boolean.valueOf(env.getProperty("spring.jpa.show-sql")));
emf.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", env.getProperty("spring.jpa.properties.hibernate.dialect"));
properties.put("hibernate.format_sql", env.getProperty("spring.jpa.properties.hibernate.format_sql"));
properties.put("hibernate.hbm2ddl.auto", env.getProperty("spring.jpa.properties.hibernate.hbm2ddl.auto"));
properties.put("hibernate.physical_naming_strategy", "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy");
properties.put("hibernate.implicit_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
emf.setJpaPropertyMap(properties);
return emf;
}

EntityManager persist and merge not working in #Transactional method

I have strange behaviour of #Transactional annotation and EntityManager persist and merge methods. I've annotated method as #Transactional and in it I call EntityManager.persists(entity)...and nothing happens. Entity is not saved to DB, no exception fired, totally nothing. I've read tons of examples, SO questions, and my code seems ok but not working.
Hibernate config:
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jndi.JndiTemplate;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Properties;
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {"com.mycompany"})
#DependsOn({"PropertiesConfig"})
public class HibernateConfig {
public HibernateConfig() {}
#Bean
public DataSource dataSource() throws NamingException {
return (DataSource) new JndiTemplate().lookup(PropertyService.getInstance().getProperty("jndi.jdbc.AGSQL"));
}
#Bean(name = "entityManager")
#DependsOn("dataSource")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
System.out.println("*** Init EntityManagerFactory ***");
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceProviderClass(HibernatePersistenceProvider.class);
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.mycompany" });
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
em.setJpaProperties(hibernateProperties());
return em;
}
#Bean(name = "tm")
public PlatformTransactionManager transactionManager() throws NamingException {
System.out.println("*** Init TransactionManager ***");
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private Properties hibernateProperties() {
return new Properties() {
{
setProperty("hibernate.dialect", PropertyService.getInstance().getProperty("hibernate.dialect"));
setProperty("show_sql", PropertyService.getInstance().getProperty("show_sql"));
setProperty("hibernate.connection.isolation", String.valueOf(Connection.TRANSACTION_READ_UNCOMMITTED));
}
};
}
}
Entity:
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import java.time.LocalDateTime;
#Entity
#Table(name = "Vehicle", schema = "dbo")
public class VehicleEntity {
private Long deviceId;
private LocalDateTime dtEdit;
private String name;
#Id
#Column(name = "device_id")
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
#Basic
#Column(name = "dt_edit")
#UpdateTimestamp
public LocalDateTime getDtEdit() {
return dtEdit;
}
public void setDtEdit(LocalDateTime dtEdit) {
this.dtEdit = dtEdit;
}
#Basic
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Dao:
import com.mycompany.VehicleEntity;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
#Repository
public class VehicleDao {
#PersistenceContext(unitName = "entityManager")
private EntityManager entityManager;
protected EntityManager getEntityManager() {
return entityManager;
}
#Transactional(transactionManager = "tm")
public void persist(VehicleEntity entity) {
getEntityManager().persist(entity);
}
#Transactional("tm")
public void merge(VehicleEntity entity) {
getEntityManager().merge(entity);
}
}
And executing this code do totally nothing:
VehicleEntity ve = new VehicleEntity();
ve.setDeviceId(111L);
ve.setName("111");
vehicleDao.persist(ve);
I tried to check transaction status inside persist method:
((Session)this.getEntityManager().getDelegate()).getTransaction().getStatus() = NOT_ACTIVE
So I can conclude that transaction has not started, or started, but entitymanager did not see it automatically (see below). In one of the questions I saw that before calling persist(entity) entity manager was joined to the transaction and made the same thing:
#Transactional(transactionManager = "tm")
public void persist(T entity) {
getEntityManager().joinTransaction();
getEntityManager().persist(entity);
}
Aaaaand...it works. And transaction status become ACTIVE.
So my questions are:
- why it did not work without joinTransaction()? I've never seen it in examples...
- (possibly after solving 1st question this one will not make sense) persisting and merging actually are in abstract dao class, but I have many entities, many entities dao and many custom methods in them. And calling joinTransaction() in each of them is not good idea. How make it in pretty way?
EDIT:
Checked one more thing - added propagation property:
#Transactional(transactionManager = "tm", propagation = Propagation.MANDATORY)
public void persist(T entity) {
getEntityManager().persist(entity);
}
And it throws an exception:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
EDIT2:
Spring version 5.1.7.RELEASE
Hibernate version 5.4.10.Final
javax.persistence:javax.persistence-api:2.2 (dependency from hibernate-core)
Finally found the problem: I was migrating from SessionFactory to EntityManager and leave in config file old settings for SessionFactory. And the issue was that transaction managers has the same method names for SF and EM:
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(sessionFactory);
return transactionManager;
}
#Bean(name = "tm")
public PlatformTransactionManager transactionManager(#Qualifier("customEntityManager") EntityManagerFactory emf) {
System.out.println("*** Init TransactionManager ***");
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
This is mistake though bean names are different.
Changing SF transaction manager method to sessionTransactionManager(...) resolve the issue.

Using two EntityManagerFactory within one spring boot app

I am trying to configure two EntityManagerFactory for one spring boot application. Each of those EntityManagerFactorys should work with different databases. Also would be great if one of them would be used as a default (e.g. when not explicit information was provided during on which one should be used).
Here is my code:
pkg/Application.java
package pkg;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).run(args);
}
}
pkg/PrimaryDbConfig.java
package pkg;
import org.h2.jdbcx.JdbcDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
#Configuration
public class PrimaryDbConfig {
public static final String DB = "primary";
#Bean
#Primary
public DataSource primaryDataSource() {
JdbcDataSource ds = new JdbcDataSource();
ds.setURL("jdbc:h2:file:~\\db\\test;AUTO_SERVER=TRUE");
ds.setUser("sa");
ds.setPassword("sa");
return ds;
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setPersistenceUnitName(DB);
factoryBean.setDataSource(primaryDataSource());
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
factoryBean.setPackagesToScan("pkg.entities");
return factoryBean;
}
#Bean
#Primary
public PlatformTransactionManager primaryTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(primaryEntityManagerFactoryBean().getObject());
return transactionManager;
}
}
pkg/SecondaryDbConfig.java
package pkg;
import org.h2.jdbcx.JdbcDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
#Configuration
public class SecondaryDbConfig {
public static final String DB = "secondary";
#Bean
public DataSource secondaryDataSource() {
JdbcDataSource ds = new JdbcDataSource();
ds.setURL("jdbc:h2:file:~\\db\\test2;AUTO_SERVER=TRUE");
ds.setUser("sa");
ds.setPassword("sa");
return ds;
}
#Bean
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setPersistenceUnitName(DB);
factoryBean.setDataSource(secondaryDataSource());
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
factoryBean.setPackagesToScan("pkg.entities");
return factoryBean;
}
#Bean
public PlatformTransactionManager secondaryTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(secondaryEntityManagerFactoryBean().getObject());
return transactionManager;
}
}
pkg/repositories/PrimaryRepository.java
package pkg.repositories;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import pkg.entities.SomeEntity;
import javax.persistence.EntityManager;
#Repository
#Transactional
public class PrimaryRepository {
private final EntityManager entityManager;
public PrimaryRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
public SomeEntity find(Integer id) {
return entityManager.find(SomeEntity.class, id);
}
}
pkg/repositories/SecondaryRepository.java
package pkg.repositories;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import pkg.entities.SomeEntity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
#Repository
#Transactional("secondaryTransactionManager")
public class SecondaryRepository {
#PersistenceContext(unitName = "secondary")
private final EntityManager entityManager;
public SecondaryRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
public SomeEntity find(Integer id) {
return entityManager.find(SomeEntity.class, id);
}
}
build.gradle
plugins {
id 'org.springframework.boot' version '1.5.7.RELEASE'
}
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "com.h2database:h2:1.4.193"
compile "org.hibernate:hibernate-core:5.2.10.Final"
compile ("org.springframework.boot:spring-boot-starter-data-jpa:1.5.2.RELEASE") {
exclude module: 'tomcat-jdbc'
}
}
Running this app I am getting next error:
Description:
Parameter 0 of constructor in pkg.repositories.PrimaryRepository required a single bean, but 2 were found:
- org.springframework.orm.jpa.SharedEntityManagerCreator#0: defined by method 'createSharedEntityManager' in null
- org.springframework.orm.jpa.SharedEntityManagerCreator#1: defined by method 'createSharedEntityManager' in null

Spring Boot change DataSource and JPA properties at runtime

I am writing a desktop Spring Boot and Data-JPA application.
Initial settings come from application.properties (some spring.datasource.* and spring.jpa.*)
One of the features of my program is possibility to specify database settings (rdbms type,host,port,username,password and so on) via ui.
That's why I want to redefine already initialized db properties at runtime.
That's why I am finding a way to do that.
I tried to do the following:
1) I wrote custom DbConfig where DataSource bean declared in Singleton Scope.
#Configuration
public class DBConfig {
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
#Scope("singleton")
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
2) In some DBSettingsController I got the instance of this bean and update new settings:
public class DBSettingsController {
...
#Autowired DataSource dataSource;
...
public void applySettings(){
if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource){
org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
PoolConfiguration poolProperties = tomcatDataSource.getPoolProperties();
poolProperties.setUrl("new url");
poolProperties.setDriverClassName("new driver class name");
poolProperties.setUsername("new username");
poolProperties.setPassword("new password");
}
}
}
But it has no effect. Spring Data Repositories are steel using initialy initialized DataSource properties.
Also I heard about Spring Cloud Config and #RefreshScope. But i think it's a kind of overhead to run http webserver alongside of my small desktop application.
Might it is possible to write custom scope for such beans?
Or by some way bind changes made in application.properties and corresponding beans properties?
Here is my solution (it might be outdated as it was created in 2016th):
DbConfig (It does not really needed, I just added for completeness config)
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
#Configuration
public class DBConfig extends HibernateJpaAutoConfiguration {
#Value("${spring.jpa.orm}")
private String orm; // this is need for my entities declared in orm.xml located in resources directory
#SuppressWarnings("SpringJavaAutowiringInspection")
public DBConfig(DataSource dataSource, JpaProperties jpaProperties, ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider) {
super(dataSource, jpaProperties, jtaTransactionManagerProvider);
}
#Override
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder factoryBuilder)
{
final LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = super.entityManagerFactory(factoryBuilder);
entityManagerFactoryBean.setMappingResources(orm);
return entityManagerFactoryBean;
}
}
DataSourceConfig
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
#Configuration
public class DataSourceConfig {
#Bean
#Qualifier("default")
#ConfigurationProperties(prefix = "spring.datasource")
protected DataSource defaultDataSource(){
return DataSourceBuilder
.create()
.build();
}
#Bean
#Primary
#Scope("singleton")
public AbstractRoutingDataSource routingDataSource(#Autowired #Qualifier("default") DataSource defaultDataSource){
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.addDataSource(RoutingDataSource.DEFAULT,defaultDataSource);
routingDataSource.setDefaultTargetDataSource(defaultDataSource);
return routingDataSource;
}
}
My extension of RoutingDataSource:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class RoutingDataSource extends AbstractRoutingDataSource {
static final int DEFAULT = 0;
static final int NEW = 1;
private volatile int key = DEFAULT;
void setKey(int key){
this.key = key;
}
private Map<Object,Object> dataSources = new HashMap();
RoutingDataSource() {
setTargetDataSources(dataSources);
}
void addDataSource(int key, DataSource dataSource){
dataSources.put(new Integer(key),dataSource);
}
#Override
protected Object determineCurrentLookupKey() {
return new Integer(key);
}
#Override
protected DataSource determineTargetDataSource() {
return (DataSource) dataSources.get(key);
}
}
And here it's special spring component to swith datasource in runtime:
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
#Component
public class DBSettingsSwitcher {
#Autowired
private AbstractRoutingDataSource routingDataSource;
#Value("${spring.jpa.orm}")
private String ormMapping;
public void applySettings(DBSettings dbSettings){
if (routingDataSource instanceof RoutingDataSource){
// by default Spring uses DataSource from apache tomcat
DataSource dataSource = DataSourceBuilder
.create()
.username(dbSettings.getUserName())
.password(dbSettings.getPassword())
.url(dbSettings.JDBConnectionURL())
.driverClassName(dbSettings.driverClassName())
.build();
RoutingDataSource rds = (RoutingDataSource)routingDataSource;
rds.addDataSource(RoutingDataSource.NEW,dataSource);
rds.setKey(RoutingDataSource.NEW);
updateDDL(dbSettings);
}
}
private void updateDDL(DBSettings dbSettings){
/** worked on hibernate 5*/
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.applySetting("hibernate.connection.url", dbSettings.JDBConnectionURL())
.applySetting("hibernate.connection.username", dbSettings.getUserName())
.applySetting("hibernate.connection.password", dbSettings.getPassword())
.applySetting("hibernate.connection.driver_class", dbSettings.driverClassName())
.applySetting("hibernate.dialect", dbSettings.dialect())
.applySetting("show.sql", "false")
.build();
Metadata metadata = new MetadataSources()
.addResource(ormMapping)
.addPackage("specify_here_your_package_with_entities")
.getMetadataBuilder(registry)
.build();
new SchemaUpdate((MetadataImplementor) metadata).execute(false,true);
}
}
Where DB settings is just an interface (you should implement it according to your needs):
public interface DBSettings {
int getPort();
String getServer();
String getSelectedDataBaseName();
String getPassword();
String getUserName();
String dbmsType();
String JDBConnectionURL();
String driverClassName();
String dialect();
}
Having your own implementation of DBSettings and builded DBSettingsSwitcher in your Spring context, now you can just call DBSettingsSwitcher.applySettings(dbSettingsIml) and your data requests will be routed to new data source.

Hibernate, oauth2 could not obtain transaction-synchronized

I tried to make my own customDetailsService and when I tried to call server for token using http:
POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=password&username=bill&password=abc123
I got exception as a result:
<UnauthorizedException xmlns="">
<error>unauthorized</error>
<error_description>Could not obtain transaction-synchronized Session for current thread</error_description>
</UnauthorizedException>
What is more, when I debugged this I had determined that there is the problem with method userDaoImpl.findByLogin(login). But when I called it from my restController for test it worked and annotations were analogous.
#EnableTransactiomManagement is added in hibernate configuration and needed classes are annotated with #Transaction.
MyUserDetailsService
package com.maxim.spring.restcontroller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.maxim.spring.dao.UserDao;
import com.maxim.spring.model.User;
#Transactional
#Service("MyUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Autowired
#Qualifier("userDaoImpl")
private UserDao userDaoImpl;
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
User user = userDaoImpl.findByLogin(login);
if(user == null) {
System.out.println("User was not found ");
throw new UsernameNotFoundException(login);
} else {
System.out.println("Tu powinienem byc i jestem");
new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPass(), getUserRole(user));
}
return null;
}
public List<GrantedAuthority> getUserRole(User user) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_"+user.getRole()));
return authorities;
}
UserDaoImpl:
package com.maxim.spring.dao;
import java.util.List;
import org.springframework.transaction.annotation.Transactional;
import org.hibernate.Query;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;
import com.maxim.spring.model.User;
#Transactional
#Repository("userDaoImpl")
public class UserDaoImpl extends AbstractDao implements UserDao{
public User findById(Integer id) {
#Override
public User findByLogin(String login) {
System.out.println("w srodku1");
Query qr = (Query) getSession().getCurrentSession().createQuery("from User u where u.login=:login").setParameter("login", login);
System.out.println("w srodku2");
User u = (User) qr.list().get(0);
System.out.println("w srodku3");
return u;
}
}
The hibernate configuration:
#EnableTransactionManagement
#Configuration
#ComponentScan({ "com.maxim.spring.configuration" })
#PropertySource(value = { "classpath:application.properties" })
public class HibernateConfiguration {
#Autowired
private Environment environment;
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "com.maxim.spring.model", "com.maxim.spring.services" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
How to make it work? What are my mistakes?
You are using getSession().getCurrentSession().......
if you put getSession().openSession().....It will works.
more info enter this discussion
Your #Transactional method loadUserByUsername() call #Transactional method findByLogin() that is conflict, can't open 2 transactions.
Idea for you:
Remove all #Transactional above class and add #Transactional on methods that you want to be transactional. Your method loadUserByUsername() must be transactional but your method findByLogin() can't be transactional because it is called from transactional method.

Categories

Resources