#Transactional not working in Spring Boot.
Application.java :
#EnableTransactionManagement(proxyTargetClass=true)
#SpringBootApplication(exclude = {ErrorMvcAutoConfiguration.class})
public class Application {
#Autowired
private EntityManagerFactory entityManagerFactory;
public static void main(String[] args) {
System.out.println("--------------------------- Start Application ---------------------------");
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
#Bean
public SessionFactory getSessionFactory() {
if (entityManagerFactory.unwrap(SessionFactory.class) == null) {
throw new NullPointerException("factory is not a hibernate factory");
}
return entityManagerFactory.unwrap(SessionFactory.class);
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.buhryn.interviewer.models" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost:5432/interviewer");
dataSource.setUsername("postgres");
dataSource.setPassword("postgres");
return dataSource;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.setProperty("hibernate.show_sql", "false");
properties.setProperty("hibernate.format_sql", "false");
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.current_session_context_class", "org.hibernate.context.internal.ThreadLocalSessionContext");
return properties;
}
}
CandidateDao.java
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
#Repository
public class CandidateDao implements ICandidateDao{
#Autowired
SessionFactory sessionFactory;
protected Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
#Override
#Transactional
public CandidateModel create(CandidateDto candidate) {
CandidateModel candidateModel = new CandidateModel(candidate.getFirstName(), candidate.getLastName(), candidate.getEmail(), candidate.getPhone());
getCurrentSession().save(candidateModel);
return candidateModel;
}
#Override
public CandidateModel show(Long id) {
return new CandidateModel(
"new",
"new",
"new",
"new");
}
#Override
public CandidateModel update(Long id, CandidateDto candidate) {
return new CandidateModel(
"updated",
candidate.getLastName(),
candidate.getEmail(),
candidate.getPhone());
}
#Override
public void delete(Long id) {
}
}
Service Class
#Service
public class CandidateService implements ICandidateService{
#Autowired
ICandidateDao candidateDao;
#Override
public CandidateModel create(CandidateDto candidate) {
return candidateDao.create(candidate);
}
#Override
public CandidateModel show(Long id) {
return candidateDao.show(id);
}
#Override
public CandidateModel update(Long id, CandidateDto candidate) {
return candidateDao.update(id, candidate);
}
#Override
public void delete(Long id) {
candidateDao.delete(id);
}
}
Controller.class
#RestController
#RequestMapping(value = "/api/candidates")
public class CandidateController {
#Autowired
ICandidateService candidateService;
#RequestMapping(value="/{id}", method = RequestMethod.GET)
public CandidateModel show(#PathVariable("id") Long id) {
return candidateService.show(id);
}
#RequestMapping(method = RequestMethod.POST)
public CandidateModel create(#Valid #RequestBody CandidateDto candidate, BindingResult result) {
RequestValidator.validate(result);
return candidateService.create(candidate);
}
#RequestMapping(value="/{id}", method = RequestMethod.PUT)
public CandidateModel update(#PathVariable("id") Long id, #Valid #RequestBody CandidateDto candidate, BindingResult result) {
RequestValidator.validate(result);
return candidateService.update(id, candidate);
}
#RequestMapping(value="/{id}", method = RequestMethod.DELETE)
public void delete(#PathVariable("id") Long id) {
candidateService.delete(id);
}
}
When I call create method in DAO system throw exception:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: save is not valid without active transaction; nested exception is org.hibernate.HibernateException: save is not valid without active transaction
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:291)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:102)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration$MetricsFilter.doFilterInternal(MetricFilterAutoConfiguration.java:90)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
My Gradle file :
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.3.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'interviewer'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.codehaus.jackson:jackson-mapper-asl:1.9.13")
compile("com.google.code.gson:gson:2.3.1")
compile("org.springframework.data:spring-data-jpa:1.8.0.RELEASE")
compile("org.hibernate:hibernate-entitymanager:4.3.10.Final")
compile("postgresql:postgresql:9.1-901-1.jdbc4")
compile("org.aspectj:aspectjweaver:1.8.6")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
task wrapper(type: Wrapper) {
gradleVersion = '2.3'
}
And link to git repository : https://github.com/Yurii-Buhryn/interviewer
First you are using Spring Boot then use Spring Boot and let that auto configure things for you. It will configure a datasource, entitymanagerfactory, transaction manager etc.
Next you are using the wrong transaction manager, you are using JPA so you should use the JpaTransactionManager instead of the HibernateTransactionManager as that is already configured for you you can simply remove the bean definition for that.
Second your hibernate.current_session_context_class is messing up proper tx integration remove it.
Use auto-config
When you take all this into account you can basically reduce your Application class to the following.
#SpringBootApplication(exclude = {ErrorMvcAutoConfiguration.class})
#EntityScan("com.buhryn.interviewer.models")
public class Application {
public static void main(String[] args) {
System.out.println("--------------------------- Start Application ---------------------------");
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
#Bean
public SessionFactory sessionFactory(EntityManagerFactory emf) {
if (emf.unwrap(SessionFactory.class) == null) {
throw new NullPointerException("factory is not a hibernate factory");
}
return emf.unwrap(SessionFactory.class);
}
}
Next add an application.properties in src/main/resources containing the following.
# DataSource configuration
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/interviewer
# General JPA properties
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=false
# Hibernate Specific properties
spring.jpa.properties.hibernate.format_sql=false
spring.jpa.hibernate.ddl-auto=create
This will configure the datasource and JPA correctly.
Use JPA instead of plain Hibernate
Another tip instead of using the plain hibernate API simply use JPA that way you could remove the bean for the SessionFactory as well. Simply change your dao to use an EntityManager instead of a SessionFactory.
#Repository
public class CandidateDao implements ICandidateDao{
#PersistenceContext
private EntityManager em;
#Override
#Transactional
public CandidateModel create(CandidateDto candidate) {
CandidateModel candidateModel = new CandidateModel(candidate.getFirstName(), candidate.getLastName(), candidate.getEmail(), candidate.getPhone());
return em.persist(candidateModel);
}
#Override
public CandidateModel show(Long id) {
return new CandidateModel(
"new",
"new",
"new",
"new");
}
#Override
public CandidateModel update(Long id, CandidateDto candidate) {
return new CandidateModel(
"updated",
candidate.getLastName(),
candidate.getEmail(),
candidate.getPhone());
}
#Override
public void delete(Long id) {
}
}
Adding Spring Data JPA
And if you really want to benefit add Spring Data JPA into the mix and remove your DAO completely and leave only an interface. What you have now would be moved to a service class (where it belongs IMHO).
The whole repository
public interface ICandidateDao extends JpaRepository<CandidateModel, Long> {}
The modified service (which is now also transactional as it should and all business logic is in the service).
#Service
#Transactional
public class CandidateService implements ICandidateService{
#Autowired
ICandidateDao candidateDao;
#Override
public CandidateModel create(CandidateDto candidate) {
CandidateModel candidateModel = new CandidateModel(candidate.getFirstName(), candidate.getLastName(), candidate.getEmail(), candidate.getPhone());
return candidateDao.save(candidate);
}
#Override
public CandidateModel show(Long id) {
return candidateDao.findOne(id);
}
#Override
public CandidateModel update(Long id, CandidateDto candidate) {
CandidateModel cm = candidateDao.findOne(id);
// Update values.
return candidateDao.save(cm);
}
#Override
public void delete(Long id) {
candidateDao.delete(id);
}
}
Now you can also remove the bean definition for the SessionFactory reducing your Application to just a main method.
#SpringBootApplication(exclude = {ErrorMvcAutoConfiguration.class})
#EntityScan("com.buhryn.interviewer.models")
public class Application {
public static void main(String[] args) {
System.out.println("--------------------------- Start Application ---------------------------");
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
So I would strongly suggest to work with the framework instead of trying to work around the framework. As that will really simplify your developer live.
Dependencies
As a final note I would suggest removing the spring-data-jpa dependency from your dependencies and use the starter instead. The same goes for AspectJ use the AOP starter for that. Also jackson 1 isn't supported anymore so adding that dependency doesn't add anything
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-starter-aop")
compile("com.google.code.gson:gson:2.3.1")
compile("org.hibernate:hibernate-entitymanager:4.3.10.Final")
compile("postgresql:postgresql:9.1-901-1.jdbc4")
testCompile("org.springframework.boot:spring-boot-starter-test")
}
Related
Hi there I am trying to create a student webapp with spring boot but i can't seem to get it to run as an error appears every time that hibernate sessionfactory can't be found and i should include a bean type of equal type in my configuration.
I thought I properly configured the webapp properly but i cant seem to get it to find my hibernate session factory which i configured in my DAO, any help on where i'm going wrong would be appreciated.
Here Spring boot launcher class
#SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)
#ComponentScan({"model", "controller", "dao", "service"})
public class StudentsApplication {
public static void main(String[] args) {
SpringApplication.run(StudentsApplication.class, args);
}
}
Here is my DAO class
#Repository
#Transactional
public class StudentDao {
#Autowired
SessionFactory sessionFactory;
public Student getStudent(final int id) {
#SuppressWarnings("unchecked")
TypedQuery<Student> q = sessionFactory.getCurrentSession().createQuery(
"from student where = id").setParameter("id", id);
return q.getSingleResult();
}
public List<Student> getAllStudents() {
#SuppressWarnings("unchecked")
TypedQuery<Student> q = sessionFactory.getCurrentSession().createQuery(
"from student");
return q.getResultList();
}
public void addStudent(final Student student) {
sessionFactory.getCurrentSession().save(student);
}
public void updateStudent(final Student student) {
sessionFactory.getCurrentSession().saveOrUpdate(student);
}
public void deleteStudent(final int id) {
sessionFactory.getCurrentSession().createQuery(
"delete from student where = id").setParameter("id", id)
.executeUpdate();
}
}
here is my configuration class
#Configuration
#PropertySource({"classpath:application.properties"})
public class DbConfig {
#Autowired
private Environment environment;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getProperty("jdbc.url"));
dataSource.setUsername(environment.getProperty("jdbc.username"));
dataSource.setPassword(environment.getProperty("jdbc.password"));
return dataSource;
}
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
factoryBean.setDataSource(dataSource());
Properties props = new Properties();
props.put("format_sql", "true");
props.put("hibernate.show_sql", "true");
factoryBean.setHibernateProperties(props);
factoryBean.setPackagesToScan("com.alpheus.students.entity");
// factoryBean.setAnnotatedClasses(Student.class);
return factoryBean;
}
#Bean
public HibernateTransactionManager getTransactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(getSessionFactory().getObject());
return transactionManager;
}
}
here is my properties file
# Connection url for the database
spring.datasource.url=jdbc:mysql://localhost:3308/week04
spring.datasource.username=user
spring.datasource.password=pass
server.port=9999
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
#view resolver
spring.mvc.view.prefix=/views/
spring.mvc.view.suffix=.jsp
here is my controller class
#Controller
public class StudentController {
#Autowired
private StudentService studentService;
#GetMapping("/home")
public String getAllStudents(Model studentsModel) {
List<Student> listStudents = studentService.getAllStudents();
studentsModel.addAttribute("listStudents", listStudents);
return "student-list";
}
#GetMapping("/student/{id}")
public String editStudent(#PathVariable int id, Model studentModel) {
studentModel.addAttribute("student", studentService.getStudent(id));
return "student-form";
}
#PostMapping("/student/new")
public String saveStudent(#ModelAttribute("student")Student student) {
studentService.addStudent(student);
return "redirect:/";
}
#GetMapping("/student")
public String showNewForm() {
return "student-form";
}
#PostMapping("/student/update/{id}")
public String updateStudent(#ModelAttribute("student") Student student) {
studentService.updateStudent(student);
return "redirect:/";
}
#GetMapping(value = "/student/delete/{id}")
public String deleteStudent(#PathVariable int id) {
studentService.deleteStudent(id);
return "redirect:/";
}
}
I'm studying Transaction Routing in Spring, but my application has a runtime problem.
I have two MySQL databases, one for reading and one for reading/write, but my routing configuration is not working, when I apply the read-only configuration, I don't get success.
This is my configurations:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
</parent>
<groupId>br.com.multidatasources</groupId>
<artifactId>multidatasources</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>multidatasources</name>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
# Database master
master.datasource.url=jdbc:mysql://localhost:3306/billionaires?createDatabaseIfNotExist=true&useTimezone=true&serverTimezone=UTC
master.datasource.username=root
master.datasource.password=root
# Database slave
slave.datasource.url=jdbc:mysql://localhost:3307/billionaires?createDatabaseIfNotExist=true&useTimezone=true&serverTimezone=UTC
slave.datasource.username=root
slave.datasource.password=root
# Database driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA property settings
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
DataSourceType.java
public enum DataSourceType {
READ_ONLY,
READ_WRITE
}
TransactionRoutingDataSource.java
public class TransactionRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? DataSourceType.READ_ONLY : DataSourceType.READ_WRITE;
}
}
RoutingConfiguration.java
#Configuration
#EnableTransactionManagement
public class RoutingConfiguration {
private final Environment environment;
public RoutingConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
public JpaTransactionManager transactionManager(#Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(#Qualifier("routingDataSource") DataSource routingDataSource) {
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setDataSource(routingDataSource);
bean.setPackagesToScan(Billionaires.class.getPackageName());
bean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
bean.setJpaProperties(additionalProperties());
return bean;
}
#Bean
public DataSource dataSource(#Qualifier("routingDataSource") DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}
#Bean
public TransactionRoutingDataSource routingDataSource(
#Qualifier("masterDataSource") DataSource masterDataSource,
#Qualifier("slaveDataSource") DataSource slaveDataSource
) {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.READ_WRITE, masterDataSource);
dataSourceMap.put(DataSourceType.READ_ONLY, slaveDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(masterDataSource());
return routingDataSource;
}
#Bean
public DataSource masterDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getProperty("master.datasource.url"));
dataSource.setUsername(environment.getProperty("master.datasource.username"));
dataSource.setPassword(environment.getProperty("master.datasource.password"));
return dataSource;
}
#Bean
public DataSource slaveDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getProperty("slave.datasource.url"));
dataSource.setUsername(environment.getProperty("slave.datasource.username"));
dataSource.setPassword(environment.getProperty("slave.datasource.password"));
return dataSource;
}
private Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
return properties;
}
}
Billionaires.java
#Entity
#Table(name = "billionaires")
public class Billionaires {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String career;
public Billionaires() { }
public Billionaires(Long id, String firstName, String lastName, String career) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.career = career;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getCareer() {
return career;
}
public void setCareer(String career) {
this.career = career;
}
}
BillionairesRepository.java
#Repository
public interface BillionairesRepository extends JpaRepository<Billionaires, Long> {
}
BillionairesService.java
#Service
public class BillionairesService {
private final BillionairesRepository billionairesRepository;
public BillionairesService(BillionairesRepository billionairesRepository) {
this.billionairesRepository = billionairesRepository;
}
#Transactional(readOnly = true) // Should be used the READ_ONLY (This point not working)
public List<Billionaires> findAll() {
return billionairesRepository.findAll();
}
#Transactional // Should be used the READ_WRITE
public Billionaires save(Billionaires billionaires) {
return billionairesRepository.save(billionaires);
}
}
In the BillionairesService class, I apply the #Transactional(readOnly = true) on findAll method for use the READ_ONLY data source, but this is not occurring.
The findAll method should be used the READ_ONLY data source and save method should be used the READ_WRITE data source.
Can someone help me fix this problem?
I would strongly suggest to use autoconfiguration as-much as you can, it will make things a little simpler. The main key is to set to delay the getting of the connection and preparing it for the current transaction.
This can be achieved in 2 different ways.
Set the prepareConnection property of the JpaDialect to false. If you don't then the JpaTransactionManager will eagerly get Connection and prepare it for the transaction. This is even before it had time to set the current state of the transaction onto the TransactionSynchronizationManager. Which will make the call to TransactionSynchronizationManager.isCurrentTransactionReadOnly always return false (as it is set at the end of the doBegin method in the JpaTransactionManager.
Set the hibernate.connection.handling_mode to DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION. This will delay the getting of a connection and close the connection after the transaction. Without Spring this is also the default for Hibernate 5.2+ (see the Hibernate User Guide) but for legacy reasons Spring switches this to DELAYED_ACQUISITION_AND_HOLD.
Either of these solutions will work as the preparing of the connection is delayed and the JpaTransactionManager has thus time to sync the state in the TransactionSynchronizationManager.
#Bean
public BeanPostProcessor dialectProcessor() {
return new BeanPostProcessor() {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HibernateJpaVendorAdapter) {
((HibernateJpaVendorAdapter) bean).getJpaDialect().setPrepareConnection(false);
}
return bean;
}
};
}
However adding this property to your application.properties will also work:
spring.jpa.properties.hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION
With either one of these solutions you can now ditch your transaction configuration, jpa etc. There is also an easier way to configure multiple datasources. It is described in the Spring Boot Reference Guide which will reuse as much of the Spring auto-configuration as possible.
First make sure the following is in your application.properties
# DATABASE MASTER PROPERTIES
master.datasource.url=jdbc:h2:mem:masterdb;DB_CLOSE_DELAY=-1
master.datasource.username=sa
master.datasource.password=sa
master.datasource.configuration.pool-name=Master-DB
# DATABASE SLAVE PROPERTIES
slave.datasource.url=jdbc:h2:mem:slavedb;DB_CLOSE_DELAY=-1
slave.datasource.username=sa
slave.datasource.password=sa
slave.datasource.configuration.pool-name=Slave-DB
# JPA PROPERTIES SETTINGS
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
spring.jpa.open-in-view=false
# ENABLE ERRORS IN DESERIALIZATION OF MISSING OR IGNORED PROPERTIES
spring.jackson.deserialization.fail-on-unknown-properties=true
spring.jackson.deserialization.fail-on-ignored-properties=true
# ENABLE ERRORS ON REQUESTS FOR NON-EXISTENT RESOURCES
spring.mvc.throw-exception-if-no-handler-found=true
# DISABLE MAPPINGS OF STATIC RESOURCES (IS NOT USABLE IN DEVELOPMENT OF APIs)
spring.web.resources.add-mappings=false
NOTE: Removed the driver for JDBC (not needed) only set spring.jpa.database-platform you set either database or database-platform not both.
Now with this and the following #Configuration class you will have 2 datasources, the routing one and the BeanPostProcessor as mentioned above (if you choose to use the property you can remove said BeanPostProcessor.
#Configuration
public class DatasourceConfiguration {
#Bean
#ConfigurationProperties("master.datasource")
public DataSourceProperties masterDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("master.datasource.configuration")
public HikariDataSource masterDataSource(DataSourceProperties masterDataSourceProperties) {
return masterDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean
#ConfigurationProperties("slave.datasource")
public DataSourceProperties slaveDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("slave.datasource.configuration")
public HikariDataSource slaveDataSource(DataSourceProperties slaveDataSourceProperties) {
return slaveDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean
#Primary
public TransactionRoutingDataSource routingDataSource(DataSource masterDataSource, DataSource slaveDataSource) {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.READ_WRITE, masterDataSource);
dataSourceMap.put(DataSourceType.READ_ONLY, slaveDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(masterDataSource);
return routingDataSource;
}
#Bean
public BeanPostProcessor dialectProcessor() {
return new BeanPostProcessor() {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HibernateJpaVendorAdapter) {
((HibernateJpaVendorAdapter) bean).getJpaDialect().setPrepareConnection(false);
}
return bean;
}
};
}
}
This will set up everything you need for this to work and still be able to use as much of the auto-configuration and detection as you can. With this, the only configuration you need to do is this DataSource setup. No JPA, Transaction management etc. as that will be done automatically.
Finally here is a test to test this with (you can test both scenarios). The read-only one will fail because there is no schema there, the save will succeed as there is a schema on the READ_WRITE side of things.
#Test
void testDatabaseSwitch() {
Assertions.assertThatThrownBy(() -> billionaireService.findAll())
.isInstanceOf(DataAccessException.class);
Billionaire newBIllionaire = new Billionaire(null, "Marten", "Deinum", "Spring Nerd.");
billionaireService.save(newBIllionaire);
}
I solved this problem by changing my implementation of the RoutingConfiguration.java class.
I configured the data source for using the setAutoCommit(false) configuration and added the property hibernate.connection.provider_disables_autocommit with value true.
#Configuration
#EnableTransactionManagement
public class RoutingConfiguration {
private final Environment environment;
public RoutingConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(#Qualifier("routingDataSource") DataSource routingDataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPersistenceUnitName(getClass().getSimpleName());
entityManagerFactoryBean.setPersistenceProvider(new HibernatePersistenceProvider());
entityManagerFactoryBean.setDataSource(routingDataSource);
entityManagerFactoryBean.setPackagesToScan(Billionaires.class.getPackageName());
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
HibernateJpaDialect jpaDialect = vendorAdapter.getJpaDialect();
jpaDialect.setPrepareConnection(false);
entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
entityManagerFactoryBean.setJpaProperties(additionalProperties());
return entityManagerFactoryBean;
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
#Bean
public TransactionTemplate transactionTemplate(EntityManagerFactory entityManagerFactory) {
return new TransactionTemplate(transactionManager(entityManagerFactory));
}
#Bean
public TransactionRoutingDataSource routingDataSource(
#Qualifier("masterDataSource") DataSource masterDataSource,
#Qualifier("slaveDataSource") DataSource slaveDataSource
) {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.READ_WRITE, masterDataSource);
dataSourceMap.put(DataSourceType.READ_ONLY, slaveDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(masterDataSource());
return routingDataSource;
}
#Bean
public DataSource masterDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getProperty("master.datasource.url"));
dataSource.setUsername(environment.getProperty("master.datasource.username"));
dataSource.setPassword(environment.getProperty("master.datasource.password"));
return connectionPoolDataSource(dataSource, determinePoolName(DataSourceType.READ_WRITE));
}
#Bean
public DataSource slaveDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getProperty("slave.datasource.url"));
dataSource.setUsername(environment.getProperty("slave.datasource.username"));
dataSource.setPassword(environment.getProperty("slave.datasource.password"));
return connectionPoolDataSource(dataSource, determinePoolName(DataSourceType.READ_ONLY));
}
private HikariDataSource connectionPoolDataSource(DataSource dataSource, String poolName) {
return new HikariDataSource(hikariConfig(dataSource, poolName));
}
private HikariConfig hikariConfig(DataSource dataSource, String poolName) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setPoolName(poolName);
hikariConfig.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 4);
hikariConfig.setDataSource(dataSource);
hikariConfig.setAutoCommit(false);
return hikariConfig;
}
private Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", environment.getProperty("spring.jpa.database-platform"));
properties.setProperty("hibernate.connection.provider_disables_autocommit", "true");
return properties;
}
private String determinePoolName(DataSourceType dataSourceType) {
return dataSourceType.getPoolName().concat("-").concat(dataSourceType.name());
}
}
The hibernate.connection.provider_disables_autocommit allows the connection is acquired prior to calling the determineCurrentLookupKey method.
I'm trying to truncate a table with jpa when having multiple datasources configuartion, the command seems to be committed but actually- the data from the table is still persists and not deleted.
My code snippets below:
relevant datasource configuration
#Configuration
#EnableJpaRepositories(basePackages = "com.haaretz.jobs.newsletter_preferences_job.data.newsletter_alerts")
public class NewsletterAlertsConfig {
private Environment environment;
#Autowired
public NewsletterAlertsConfig(Environment environment){
this.environment = environment;
}
#Primary
#Bean(name = "newsletterAlertsDataSource")
public DataSource newsletterAlertsDataSource() {
return DataSourceBuilder
.create()
.driverClassName(environment.getProperty("datasource.newsletterAlerts.driverClassName"))
.url(environment.getProperty("datasource.newsletterAlerts.jdbcUrl"))
.username(environment.getProperty("datasource.newsletterAlerts.username"))
.password(environment.getProperty("datasource.newsletterAlerts.password"))
.build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("newsletterAlertsDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.haaretz.jobs.newsletter_preferences_job.data.newsletter_alerts")
.persistenceUnit("newsletterAlerts")
.build();
}
}
second datasource configured
#Configuration
#EnableJpaRepositories(basePackages = "com.haaretz.jobs.newsletter_preferences_job.data.gstat",
entityManagerFactoryRef = "gstatEntityManagerFactory")
public class GstatConfig {
private Environment environment;
#Autowired
public GstatConfig(Environment environment){
this.environment = environment;
}
#Bean(name = "gstatDataSource")
public DataSource gstatDataSource() {
return DataSourceBuilder
.create()
.driverClassName(environment.getProperty("datasource.gstat.driverClassName"))
.url(environment.getProperty("datasource.gstat.jdbcUrl"))
.username(environment.getProperty("datasource.gstat.username"))
.password(environment.getProperty("datasource.gstat.password"))
.build();
}
#Bean(name = "gstatEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean gstatEntityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("gstatDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.haaretz.jobs.newsletter_preferences_job.data.gstat")
.persistenceUnit("gstat")
.build();
}
}
repository
#Repository
public interface NewsletterAlertsRepository extends CrudRepository<NewsletterOpPersonalNl, String> {
#Modifying
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Query(
value = "truncate table OP_PERSONAL_NL",
nativeQuery = true
)
void truncate();
}
the job that activates the deletion
#Component
public class NewsletterPreferencesJob {
private NewsletterAlertsRepository newsletterAlertsRepository;
#Autowired
public NewsletterPreferencesJob(NewsletterAlertsRepository newsletterAlertsRepository) {
this.newsletterAlertsRepository = newsletterAlertsRepository;
}
#Transactional
public void doTask() {
newsletterAlertsRepository.truncate();
}
}
I've also tried to use crudRepoditory default function, which returns the same result (commited but not actually deleted):
newsletterAlertsRepository.deleteAll();
or using a transaction manager, which also doesn't actually deletes the data
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
entityManager.createNativeQuery("truncate table OP_PERSONAL_NL").executeUpdate();
}
});
I am migrating jdbc to hibernate and i have palced below hibernate configuration in my application.
public class HibernateConfiguration {
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "com.cm.models" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(jdbcurl);
dataSource.setUsername(userName);
dataSource.setPassword(password);
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.show_sql", true);
properties.put("hibernate.format_sql", true);
return properties;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
my application interacting fine with database at application startup creating hibernate session successfully through session factory giving output also.
**#Autowired
private SessionFactory sessionFactory;**
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
but after application startup when i hitting DAO by controller then session factory bean getting Null reference and throwing NullPointerException due to which unable to create or open hibernate session , i tried to find out solution but that's not working please let me know why above SessionFactory bean having nullPointer due to which issue created.
Just to test my DAO logic I am using this controller and This controller hitting to DAO where sessionFacory bean is null.
#RestController
#RequestMapping("/Emp")
public class myController {
#RequestMapping(value = "/findByChannelManager", method = RequestMethod.GET)
public void findemp() {
HotelDaoImpl hotelDaoImpl=new HotelDaoImpl();
List <HotelEntity> list = new ArrayList<>();
list = hotelDaoImpl.findByChannelManager (EnumCM.AR);
for (HotelEntity pro : list) {
System.out.println(pro);
}
}
}
#Repository
#Transactional
public class HotelDaoImpl extends AbstractDao implements IHotelDao {
#SuppressWarnings({ "unchecked", "unused" })
#Override
public List<HotelEntity> findByChannelManager(EnumCM cm) {
List<HotelEntity> list = null;
try {
Session s = getSession();
Criteria criteria=s.createCriteria(Hotel.class);
criteria.add(Restrictions.eq("channelManager", "cm.name()"));
list = criteria.list();
}catch(Exception e) {
LOGGER.debug("error " +e.getMessage());
e.printStackTrace();
}
return list;
}
public abstract class AbstractDao {
#Autowired
private SessionFactory sessionFactory;
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
}
You cant access dao from your controller. You can access dao from service so add service class. Try this code
#RestController
#RequestMapping("/Emp")
public class myController {
#Autowired
HotelService service;
#RequestMapping(value = "/findByChannelManager", method = RequestMethod.GET)
public void findemp() {
List <HotelEntity> list = new ArrayList<>();
list = service.findByChannelManager (EnumCM.AR);
for (HotelEntity pro : list) {
System.out.println(pro);
}
}
}
#Service
#Transactional
public class HotelService {
#Autowired
private HotelDao dao;
public List<HotelEntity> findByChannelManager(EnumCM cm) {
return dao.findByChannelManager(EnumCM cm);
}
}
#Repository
public class HotelDaoImpl extends AbstractDao implements IHotelDao {
#SuppressWarnings({ "unchecked", "unused" })
#Override
public List<HotelEntity> findByChannelManager(EnumCM cm) {
List<HotelEntity> list = null;
try {
Session s = getSession();
Criteria criteria=s.createCriteria(Hotel.class);
criteria.add(Restrictions.eq("channelManager", "cm.name()"));
list = criteria.list();
}catch(Exception e) {
LOGGER.debug("error " +e.getMessage());
e.printStackTrace();
}
return list;
}
public abstract class AbstractDao {
#Autowired
private SessionFactory sessionFactory;
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
}
I'm trying to save entity in repository but it does not work at all. Repository is Autowired and in runtime I use saveAndFlush to save entity. I'm using PostgreSQL. Above test methods I added comments with explanation what is going on. I expected that method saveAndFlush should work but it did not. I can not find why.
#Transactional
public class TestClass{
#Autowired private MyRepository repository;
#Autowired private EntityManager entityManager;
// Working version
public void writingToRepositoryWorking() {
entityManager.getTransaction().begin();
entityManager.persist(new MyData(99));
entityManager.getTransaction().commit();
}
// not working and throws exception :
// TransactionRequiredException: no transaction is in progress
public void writingToRepositoryNotWorking() {
repository.saveAndFlush(new MyData(99));
}
// not working, no exception, no data in repository,
// but auto generated ID is incremented
public void writingToRepositoryNotWorkingToo() {
repository.save(new MyData(99));
}
}
repository interface file
#Repository
#Transactional
public interface MyRepository extends JpaRepository<MyData, Long> {}
MyData file
#Entity(name = "myData")
public class MyData {
#Id #GeneratedValue(strategy = GenerationType.AUTO) long id;
private int testValue;
public MyData() { }
public BugData(int testValue) {
this.testValue = testValue;
}
public long getId() {
return id;
}
public int getTestValue() {
return testValue;
}
}
ApplicationConfiguration file
#Configuration
#EnableJpaRepositories("com.mypackage.app")
#EnableTransactionManagement
#PropertySource("classpath:application.properties")
#EnableWebMvc
class ApplicationConfiguration extends WebMvcConfigurationSupport {
#Value("${jdbc.url}") private String KEY_JDBC_URL;
#Value("${jdbc.username}") private String KEY_JDBC_USERNAME;
#Value("${jdbc.password}") private String KEY_JDBC_PASSWORD;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
#Autowired
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setPackagesToScan("com.mypackage.app");
factory.setHibernateProperties(hibernateProperties());
return factory;
}
public Properties hibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.hbm2ddl.auto", "update");
return properties;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
return new HibernateTransactionManager(sessionFactory);
}
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl(KEY_JDBC_URL);
dataSource.setUsername(KEY_JDBC_USERNAME);
dataSource.setPassword(KEY_JDBC_PASSWORD);
return dataSource;
}
#Bean
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.mypackage.app");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
em.setJpaProperties(hibernateProperties());
em.afterPropertiesSet();
return em.getObject();
}
#Bean
public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
...
}
For starter, you're actually working on 2 different EntityManager in your non-working test case:
EntityManager autowired into your test by Spring (this one is singleton and should be avoided anyway) ,other is
EntityManager created by the EntityManagerFactory configured in your ApplicationConfiguration.
At the same time, you also have another Session running along side the aforementioned 2 EntityManagers due to your configuration of Hibernate SessionFactory. Additionally, because of the configured HibernateTransactionManager, all transactions created by #Transactional are bound to the Hibernate's Session created by SessionFactory and the EntityManager used by your Repository certainly has no way to know about it. This is why TransactionRequiredException was thrown when your Repository tried to persist data.
To fix it, you may consider removing the Hibernate's SessionFactory and switch the transaction manager to a JpaTransactionManager. Then, #Transactional on your Repository will have the effect of creating a new transaction and binding it to the existing EntityManager that is known to Spring.
One side note is that the #Transactional on your TestClass doesn't help at all as the instance of this class is not instantiated and managed by Spring. To make this work, a proper configuration of transactional test class needs to be provided as described here: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html.
Hope this helps.