I've scheduled a job with annotation #Scheduled, which was supposed to process data and save it to database using spring data jpa. The save method was invoked without any exceptions but there was no insert to database. In the same annotated method I invoked findAll method which worked fine and fetched data. What could be a reason?
#Repository
public interface PossibleOfferLinkRepository extends PagingAndSortingRepository<PossibleOfferLink, Long> {
}
#Configuration
#ComponentScan
#EnableAutoConfiguration
#Import({Scheduler.class})
#EntityScan(basePackages="model_package")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#EnableScheduling
#ConditionalOnProperty(value= "property_name")
public class Scheduler {
...
#Scheduled(fixedRate=100000)
public void scheduleCrawlerJob() throws MalformedURLException {
Iterable<PossibleOfferLink> links = repo.findAll();
PossibleOfferLink link = repo.save(new PossibleOfferLink(new URL("...")));
}
}
Maven
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.182</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<version>${easytesting.version}</version>
</dependency>
</dependencies>
Finally i solved this issue in this way :
Added #EnableTransactionManagement in Application class and also added
PlatformTransactionManager Bean. Check the bellow code:
#Autowired
private EntityManagerFactory entityManagerFactory;
#Bean
public PlatformTransactionManager transactionManager()
{
return new JpaTransactionManager(entityManagerFactory);
}
Here is the imports :
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
In my scheduler code added bellow code:
#Scheduled(fixedRate = 60 *10*1000)
#Transactional(propagation=Propagation.REQUIRES_NEW)
public void reportCurrentTime() {
doDatabaseTransaction();
}
Here is the imports :
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
Hope this will solve your problem :)
Your problem is due to there aren't Transaction in progress to commit.
Cause:
javax.persistence.TransactionRequiredException: no transaction is in progress
Here an example on how to configure transaction manager by annotation.
Here the reference
#Configuration
#EnableTransactionManagement
public class PersistenceJPAConfig{
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(){
...
}
#Bean
public PlatformTransactionManager transactionManager(){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
entityManagerFactoryBean().getObject() );
return transactionManager;
}
}
Add on scheduleCrawlerJob #Transactional
#Scheduled(fixedRate=100000)
#Transactional
public void scheduleCrawlerJob() throws MalformedURLException {
Iterable<PossibleOfferLink> links = repo.findAll();
PossibleOfferLink link = repo.save(new PossibleOfferLink(new URL("...")));
}
I got such issue solved with wrapping the body of scheduleCrawlerJob() to a separate method annotated with #Transactional of another class and creating a method annotated with #Async that is called from scheduleCrawlerJob()
So overall chain of calls would look like:
Scheduled -> Async -> Transactional
P.S. don't forget to add #EnableAsync to the configuration
Related
I am trying to connect my e-commerce project backend to swagger2. I have installed all the dependencies, yet I still cannot do it.
This is the dependency declared in my pom.xml file:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
This is one of my user controller files:
#RestController
#RequestMapping("/api")
#CrossOrigin
public class UserController {
#Autowired
private UserRepository userRepository;
#GetMapping("/")
public List<User> GetUsers() {
return userRepository.findAll();
}
#GetMapping("/{id}")
public User GetUser(#PathVariable String id) {
return userRepository.findById(id).orElse(null);
}
#PostMapping("/")
public User postMethodName(#RequestBody User user) {
return userRepository.save(user);
}
#PutMapping("/")
public User PutMapping(#RequestBody User newUser) {
User oldUser = userRepository.findById(newUser.getId()).orElse(null);
oldUser.setName(newUser.getName());
oldUser.setEmail(newUser.getEmail());
oldUser.setPassword(newUser.getPassword());
userRepository.save(oldUser);
return oldUser;
}
#DeleteMapping("/{id}")
public String DeleteUser(#PathVariable String id) {
userRepository.deleteById(id);
return id;
}
}
This is the code of my main application:
package com.omazon.ecommerce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#SpringBootApplication
#EnableSwagger2
public class ECommerceApplication {
public static void main(String[] args) {
SpringApplication.run(ECommerceApplication.class, args);
}
}
Lastly, this is what I declared in the application.properties file:
spring.data.mongodb.uri=mongodb://localhost:27017/e-commerce
This is the picture of the error I got:
Swagger2's usage seems to require (or at least often includes) the concept of a Docket api via an instantiation such as new Docket() or new Docket(DocumentationType.SWAGGER_2). I don't see that in your code snippets, so wonder if that may be one issue.
Per the swagger docs, Docket is Springfox’s primary api configuration mechanism.
Specifically, this section regarding configuration may be helpful. Note the Docket instantiation:
...
#Bean //Don't forget the #Bean annotation
public Docket customImplementation(){
return new Docket()
.apiInfo(apiInfo());
//... more options available
...
There's also a more complete example in those same docs here.
For reference, there's another usage example in this tutorial.
I have a problem, I'm trying to use two Oracle databases in spring boot using DataSource, The DataSource wiht the #Primay annotation works fine but the oher one only gives me:
SQL Error: 942, SQLState: 42000
ORA-00942: table or view does not exist
I don't have idea what I should do now. Any help will be welcomed. Thanks!
application.properties
spring.datasource.url=jdbc\:oracle\:thin\:[connection] #Not showing for security
spring.datasource.password=[password]
spring.datasource.configuration.maximum-pool-size=30
spring.sgc-datasource.url=jdbc\:oracle\:[connection] #Not showing for security
spring.sgc-datasource.username=[user]
spring.sgc-datasource.password=[password]
spring.sgc-datasource.max-total=30
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
spring.jpa.database=default
spring.jpa.hibernate.ddl-auto=none
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.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.indra.vmo.edenorte</groupId>
<artifactId>InMpData</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>InMpData</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
DatabaseConfiguration.java
package com.indra.vmo.edenorte.config;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
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 com.zaxxer.hikari.HikariDataSource;
#Configuration(proxyBeanMethods = false)
public class DatabaseConfiguration {
#Bean
#Primary
#ConfigurationProperties("spring.datasource")
public DataSourceProperties inMpDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("spring.datasource.configuration")
public HikariDataSource inMpDataSource(DataSourceProperties inMpDataSourceProperties) {
return inMpDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean
#ConfigurationProperties("spring.sgc-datasource")
public DataSourceProperties sgcDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("spring.sgc-datasource.configuration")
public HikariDataSource sgcDataSource(DataSourceProperties sgcDataSourceProperties) {
return sgcDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
}
Repository from 1st DB
package com.indra.vmo.edenorte.repository.inmp;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.indra.vmo.edenorte.entity.inmp.MpLocalidadesCoordenadas;
#Repository
public interface IMpLocalidadesCoordenadasRepository extends JpaRepository<MpLocalidadesCoordenadas, String> {
}
Repository from 2nd DB
package com.indra.vmo.edenorte.repository.sgc;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.indra.vmo.edenorte.entity.sgc.Clientes;
#Repository
public interface IClientesRepository extends JpaRepository<Clientes, Integer> {
#Query("select c from Clientes c where c.docId=?1")
public Clientes findBydocId(String docId);
#Query("select c from Clientes c where c.docId=?1 and c.tipDoc=?2")
public Clientes findBydocIdU(String docId, String tipDoc);
}
SpringBoot auto-config works perfectly for a single datasource. For multiple datasources, you will need to manually configure the EntityManagerFactory beans for each of the datasources. See this article
I could resolve the problem with the following changes in my code:
application.properties
I changed the spring.jpa.hibernate.ddl-auto from none to validate and made some other chages
#Credenciales Datasource (InMpData)
spring.datasource.url=jdbc\:oracle\:thin\:[connection] #Not showing for security
spring.datasource.username=[user]
spring.datasource.password=[password]
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
#Credenciales Datasource (SGC)
spring.sgcdatasource.url=jdbc\:oracle\:thin\:[connection] #Not showing for security
spring.sgcdatasource.username=[user]
spring.sgcdatasource.password=[password]
spring.sgcdatasource.driver-class-name=oracle.jdbc.driver.OracleDriver
#Hibernate config
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
spring.jpa.hibernate.ddl-auto=validate
# HikariCP settings
# spring.datasource.hikari.*
spring.datasource.hikari.connection-timeout=60000
spring.datasource.hikari.maximum-pool-size=5
pom.xml
Added a new dependency
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
Divided the DatabaseConfiguration.java in two separeated files InMpConfig.java and SgcConfig.java
InMpConfig.java
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "com.indra.vmo.edenorte.repository.inmp", entityManagerFactoryRef = "inMpEntityManagerFactory", transactionManagerRef = "inMpTransactionManager")
public class InMpConfig {
#Bean
#Primary
#ConfigurationProperties("spring.datasource")
public DataSourceProperties inMpDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("spring.datasource.configuration")
public DataSource inMpDataSource() {
return inMpDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
/*Primary Entity manager*/
#Primary
#Bean(name = "inMpEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean inMpEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(inMpDataSource()).packages("com.indra.vmo.edenorte.entity.inmp").build();
}
#Primary
#Bean
public PlatformTransactionManager inMpTransactionManager(
final #Qualifier("inMpEntityManagerFactory") LocalContainerEntityManagerFactoryBean inMpEntityManagerFactory) {
return new JpaTransactionManager(inMpEntityManagerFactory.getObject());
}
}
SgcConfig.java
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "com.indra.vmo.edenorte.repository.sgc", entityManagerFactoryRef = "sgcEntityManagerFactory", transactionManagerRef = "sgcTransactionManager")
public class SgcConfig {
#Bean
#ConfigurationProperties("spring.sgcdatasource")
public DataSourceProperties sgcDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("spring.sgcdatasource.configuration")
public DataSource sgcDataSource() {
return sgcDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean(name = "sgcEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean sgcEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(sgcDataSource()).packages("com.indra.vmo.edenorte.entity.sgc").build();
}
#Bean(name = "sgcTransactionManager")
public PlatformTransactionManager sgcTransactionManager(
final #Qualifier("sgcEntityManagerFactory") LocalContainerEntityManagerFactoryBean sgcEntityManagerFactory) {
return new JpaTransactionManager(sgcEntityManagerFactory.getObject());
}
}
And made some changes to the models, such as add the #Table and #Column annotation
#Entity
#Data
#Table(name = "CLIENTES")
public class Clientes implements Serializable {
#Id
#NotNull
#Column(name = "COD_CLI")
private Integer codCli;
#Column(name = "USUARIO")
private String usuario;
#Column(name = "F_ACTUAL")
private Date fActual;
I hope this is helpful for someone else.
I'm using Spring Boot 1.5.9 with spring-boot-starter-data-jpa and Camel 2.20.1.
As input I get an XML file with a sequence of elements, which I unmarshal with JAXB and after that I aggregate them to a list of elements.
For that I defined a Element class where I associate a root element for JAXB and annotated it as a JPA entity.
#Entity
#Table (name="tblElement", schema="comp")
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Element implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column (name="name")
#XmlElement(required = true)
private String name;
public void Element(){}
//getter and setter methods
...
}
So my camel route processes a list of entries which I want to store to a MySQL database (version: 5.1.73).
#Component
public class CompRoute extends RouteBuilder {
#Autowired
private ElementDao elementDao;
#Override
public void configure() throws Exception {
DataFormat jaxbDataFormat = new JaxbDataFormat("com.comp.beans");
from(file:src/test/resources/input)
.convertBodyTo(byte[].class, "UTF-8")
.split().tokenizeXML("element")
.unmarshal(jaxbDataFormat)
.setProperty("SplitSize", simple("${header.CamelSplitSize}"))
.aggregate(constant(true), new ArrayListAggregationStrategy())
.completionSize(simple("${property.SplitSize}"))
.bean(elementDao, "insertElementList");
}
}
I'm not so familiar with JPA and the transaction manager, so I configured it according these documentations:
https://spring.io/guides/gs/accessing-data-jpa/
https://docs.spring.io/spring-data/jpa/docs/1.5.0.RELEASE/reference/html/jpa.repositories.html
application.properties
spring.datasource.url=jdbc:mysql://compserver:3306/comp
spring.datasource.username=user
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
JpaConfig.java
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = CompApplication.class)
class JpaConfig {
#Autowired
DataSource dataSource;
#Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
String entities = ClassUtils.getPackageName(CompApplication.class);
factory.setPackagesToScan(entities);
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
#Qualifier (value = "jpaTransactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
}
CompApplication.java
#SpringBootApplication
public class CompApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(CompApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
}
ElementDao.java
#Service
public class ElementDao {
#Autowired
ElementRepository elementRepository;
#Transactional (transactionManager = "jpaTransactionManager", readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void insertElementList(Exchange exchange) {
if(exchange.getIn().getBody() instanceof List) {
List<Element> elements= convertListToElementList(exchange.getIn().getBody(List.class));
if (elements != null) {
elementRepository.save(elements);
}
}
}
}
ElementRepository.java
#Repository
public interface ElementRepository extends CrudRepository<Element, Long> {
}
But my transaction configuration is not working correct. Because if an error occured e.g. while storing the 5th element, the whole transaction is not rolled back. It should not insert any elements. But still the first 4 elements are stored and commited.
I don't understand this behavor? How can I set my service #insertElementList transactional that the whole operation is rolled back when an exception occured during database storage?
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company</groupId>
<artifactId>company</artifactId>
<version>0.0.1</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<camel.version>2.20.1</camel.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jdbc</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jaxb</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jms</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>com.ibm</groupId>
<artifactId>com.ibm.mq.allclient</artifactId>
<version>9.0.0.1</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>4.8</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
</project>
I found the solution. The problem was the database and not my configuration. The MySql table was of type MyISAM, which does not support transactions and roll back. So I converted the table to InnoDB and now it works - when the transaction fails, all is rolled back.
According to Spring documentation.
#Transactional on beans in the same application context it is defined in. This means that, if you put annotation in a WebApplicationContext for a DispatcherServlet, it only checks for #Transactional beans in your controllers, and not your services.
So, your save method is not transactional , include that package also in which
save method is defined.
I'm trying to write an integration test for my TemplateRepository using an inmemory db(HSQL).
public interface TemplateRepository extends CrudRepository<TemplateEntity, String> {
TemplateEntity findByIdAndTemplateName(String id, String templateName);
void deleteByIdAndTemplateName(String cifId, String templateName);
}
This is the test class so far:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestConfig.class})
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest("server.port:0")
public class TemplatesRepositoryTest {
#Autowired
private TemplateRepository templateRepository;
private EmbeddedDatabase db;
#Before
public void setUp() {
db = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("sql/create-db.sql")
.addScript("sql/insert-data.sql")
.build();
}
#Test
public void test1() { }
}
Which is using this context config:
#Configuration
public class TestConfig {
#Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder
.setType(EmbeddedDatabaseType.HSQL)
.addScript("sql/create-db.sql")
.addScript("sql/insert-data.sql")
.build();
return db;
}
}
And the application class:
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
And the declared dependencies:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The autowiring of the repository to the test class is not working. I'm getting the following error:
11:25:57.300 [main] ERROR o.s.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener#5f3a4b84] to prepare test instance [TemplatesRepositoryTest#4eb7f003]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'TemplatesRepositoryTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private TemplateRepository TemplatesRepositoryTest.templateRepository; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [TemplateRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I have tried the solutions in all the related topics found, but none seems to fix the problem. Could someone point out what I'm missing here?
I'm using SpringBoot 1.3.1.RELEASE and SpringDataJpa 1.9.2.RELEASE.
I was fighting the same issue and found that when using autoconfiguration for CrudRepository, PagingAndSortingRepository and JpaRepository one should not call initialization scripts within DataSource bean as the content of the in-memory database as H2 is overwritten by the DatabasePopulator bean. Try the configuration code as below:
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
#Configuration
public class TestConfig {
#Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.HSQL).build();
return db;
}
#Bean
public DatabasePopulator databasePopulator(DataSource dataSource) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setContinueOnError(true);
populator.setIgnoreFailedDrops(true);
populator.addScript(new ClassPathResource("sql/create-db.sql"));
populator.addScript(new ClassPathResource("sql/insert-data.sql"));
try {
populator.populate(dataSource.getConnection());
} catch (SQLException ignored) {
}
return populator;
}
}
In case of "solid" database, as for example MySQL, one may explicitly provide NULL value:
#Bean
public DatabasePopulator databasePopulator() {
return null;
}
Hope it solves your problem
I have 2 repositories, one for mongodb (DocumentRepository) and the other for hibernate entity (EntityRepository)
I have a simple service:
#Transactional
public doSomePersisting() {
try {
this.entityRepository.save(entity);
this.documentRepository.save(document);
}
catch(...) {
//Rollback mongoDB here
}
}
Is it possible to rollback the mongoDB on the "//Rollback mongoDB here" line?
I already got a rollback from the entity part (Transactional annotation)
MongoDB doesn't support transactions (at least not outside the scope of a single document). If you want to roll back changes you will need to handcraft that yourself. There are a few resources out there that describe ways of implementing your own transactions in Mongo if you really need them in certain circumstances. You could take a look at..
http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/
This is just an explanation of a pattern you could use. If you find that you absolutely need transactions in your application, you should consider whether MongoDB is a good fit for your needs.
Sorry for reposting my answer.
The earlier code was allowed to insert data into MongoDB even query exceptions throwing at data insertion into PostgreSQL(Using myBatis).
I have resolved the data Transaction issue between MongoDB and Relational database and #Transactional perfectly works by making these changes in the above code.
Solution for #Transactional Management.
Mongo Config class
#Configuration
public class MongoConfig extends AbstractMongoConfiguration{
private static final Logger LOG = LoggerFactory.getLogger(MongoConfig.class);
#Value("${spring.data.mongodb.database}")
private String dbName;
#Value("${spring.data.mongodb.host}")
private String dbHost;
#Value("${spring.data.mongodb.port}")
private int dbPort;
#Override
public String getDatabaseName() {
return dbName;
}
#Bean
public MongoClient mongoClient(){
return new MongoClient(dbHost, dbPort);
}
#Bean
public MongoDbFactory mongoDbFactory(){
return new SimpleMongoDbFactory(mongoClient(),dbName);
}
#Bean
public MongoTemplate mongoTemplate() {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
// Don't save _class to mongo
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(),mappingMongoConverter);
mongoTemplate.setSessionSynchronization(SessionSynchronization.ON_ACTUAL_TRANSACTION);
return mongoTemplate;
}
public MongoTemplate fetchMongoTemplate(int projectId) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
// Don't save _class to mongo
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
MongoDbFactory customizedDBFactory = new SimpleMongoDbFactory(mongoClient(), dbName+"_"+projectId);
MongoTemplate mongoTemplate = new MongoTemplate(customizedDBFactory,mappingMongoConverter);
MongoTransactionManager mongoTransactionManager = new MongoTransactionManager(customizedDBFactory);
return mongoTemplate;
}
#Bean
public MongoTransactionManager mongoTransactionManager() {
return new MongoTransactionManager(mongoDbFactory());
}
}
Service class for Data insertion
#Service
#Component
public class TestRepositoryImpl implements TestRepository{
private static final Logger LOG = LoggerFactory.getLogger(TestRepositoryImpl.class);
#Autowired MongoConfig mongoConfig;
#Autowired MongoTemplate mongoTemplate;
#Autowired MongoTransactionManager mongoTransactionManager;
#Autowired UserService userService;
#Override
#Transactional
public void save(Test test){
int projectId = 100;
if (projectId != 0) {
mongoTemplate = mongoConfig.fetchMongoTemplate(100);
mongoTemplate.setSessionSynchronization(SessionSynchronization.ALWAYS);
}
mongoTemplate.insert(test);
IdName idName = new IdName();
idName.setName("test");
mongoTemplate.insert(idName);
User user = new User();
user.setName("Demo");
user.setEmail("srini#abspl.in");
user.setPassword("sdfsdfsdf");
userService.save(user);
}
}
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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.abcplusd.sample.mongoapi</groupId>
<artifactId>sample-mongo-api</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Sample Spring Boot Mongo API</name>
<description>Demo project for Spring Boot Mongo with Spring Data Mongo</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</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-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.1.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.8.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.2</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
With MongoDb 4.0.x you can use transactions. If you use a version below you have to implement a two phase commit.
NB: MongoDb allows you to use transactions only if you have a ReplicaSet.
In order to use a transaction for both JPA and MongoDb you have to use a ChainedTransactionManager. The process is :
create Jpa Transaction manager
create MongoDb transaction manager
create ChainedTransactionManager which will use the two above
My conf looks like this (I don't use spring boot, but it should be equivalent) :
Jpa configuration
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories("com....")
public class HibernateConfig {
//define entity manager, data source and all the stuff needed for your DB
#Bean("jpaTransactionManager")
public JpaTransactionManager transactionManager() throws NamingException {
JpaTransactionManager transactionManager = new JpaTransactionManager();
//transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
MongoDb configuration
#Configuration
#EnableMongoRepositories(basePackages = "com....")
public class MongoDbConf extends AbstractMongoClientConfiguration {
private final Environment environment;
#Autowired
public MongoDbConf(Environment environment) {
this.environment = environment;
}
#Override
public MongoClient mongoClient() {
String connectionString = environment.getProperty("mongodb.connectionString");
if(StringUtils.isBlank(connectionString))
throw new IllegalArgumentException("No connection string to initialize mongo client");
return MongoClients.create(
MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(connectionString))
.applicationName("MY_APP")
.build());
}
#Override
protected String getDatabaseName() {
return environment.getProperty("mongodb.database", "myDB");
}
#Bean("mongoDbTransactionManager")
public MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
}
ChainedTransactionManager configuration
#Configuration
public class ChainedTransactionConf {
private MongoTransactionManager mongoTransactionManager;
private JpaTransactionManager jpaTransactionManager;
#Autowired
public ChainedTransactionConf(MongoTransactionManager mongoTransactionManager, JpaTransactionManager jpaTransactionManager) {
this.mongoTransactionManager = mongoTransactionManager;
this.jpaTransactionManager = jpaTransactionManager;
}
#Bean("chainedTransactionManager")
public PlatformTransactionManager getTransactionManager() {
ChainedTransactionManager transactionManager = new ChainedTransactionManager(jpaTransactionManager, mongoTransactionManager);
return transactionManager;
}
}
Example of a mongoDb repo
#Service
public class MongoDbRepositoryImpl implements MongoDbRepository {
private static final Logger logger = Logger.getLogger(MongoDbRepositoryImpl.class);
//MongoOperations will handle a mongo session
private final MongoOperations operations;
#Autowired
public MongoDbRepositoryImpl(MongoOperations operations) {
this.operations = operations;
}
#Override
public void insertData(Document document) {
MongoCollection<Document> collection = operations.getCollection("myCollection");
collection.insertOne(document);
}
Using transaction in your service
#Service
public class DocumentServiceImpl implements DocumentService {
private final MongoDbRepository mongoDbRepository;
private final JpaRepository jpaRepository;
#Autowired
public DocumentServiceImpl(MongoDbRepository mongoDbRepository,JpaRepository jpaRepository) {
this.mongoDbRepository = mongoDbRepository;
this.jpaRepository = jpaRepository;
}
#Override
#Transactional("chainedTransactionManager")
public void insertNewDoc(Map<String,Object> rawData) {
//use org.springframework.transaction.annotation.Transactional so you can define used transactionManager
//jpaRepository.insert...
Document mongoDoc = new Document(rawData);
mongoDbRepository.insertData(mongoDoc)
//you can test like this : breakpoint and throw new IllegalStateException()
//to see that data is not commited
}
MongoDB v4.x.x works perfectly with #Transactional, they have explicit support for this by using the following dependency and repository :-
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>Lovelace-M3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
And a MongoTransactionConfig class:-
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
#Configuration
#EnableMongoRepositories(basePackages = "com.airtel.africa.caxr.repository")
public class MongoTransactionConfig extends AbstractMongoClientConfiguration {
#Value("${spring.data.mongodb.host}")
private String host;
#Value("${spring.data.mongodb.port}")
private String port;
#Value("${spring.data.mongodb.database}")
private String database;
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
#Override
protected String getDatabaseName() {
return database;
}
#Override
public MongoClient mongoClient() {
String connectionString = "mongodb://"+host+":"+port;
return MongoClients.create(MongoClientSettings.builder()
.applyConnectionString(new
ConnectionString(connectionString)).build());
}
}
And here I'm using mongo with kafka as a 1 transaction so if any checked or unchecked exception occurs here then mongo transaction should be rolled back so I used #Transactional(rollbackFor = Exception.class):-
#Transactional(rollbackFor = Exception.class)
public void receiveInEventRequest(TransactionDto transactionDto) throws
InterruptedException, ExecutionException {
// db insert
TransactionRequest transactionRequest = requestDbDumpService.dumpToDb(transactionDto);
// kafka insert
ListenableFuture<SendResult<String, TransactionDto>> kafkaResult = kafkaTemplate.send(kafkaProducerQueueName, “ID”, transactionDto);
SendResult<String, TransactionDto> kafkaSendResult = kafkaResult.get();
}
If anyone is in need of transactional support for reactive style spring boot and MongoDb integration then please go through the answer here