I am writing unit test cases for below class . I'm trying to mock admin client , so that i can call the below method create topic. But getting null pointer exception.
#Service
public class TopicService {
private static final Logger LOG = LoggerFactory.getLogger(TopicService.class);
#Autowired
private AdminClient adminClient;
public void createTopic(Topic topic) throws ExecutionException, InterruptedException {
adminClient
.createTopics(Collections.singletonList(ServiceHelper.fromTopic(topic)))
.values()
.get(topic.getName())
.get();
}
}
The unit test case is as follows
package org.kafka.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.CreateTopicsResult;
import org.apache.kafka.clients.admin.ListTopicsResult;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.common.KafkaFuture;
import org.apache.kafka.common.internals.KafkaFutureImpl;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kafka.model.Topic;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.*;
import java.util.concurrent.ExecutionException;
import static org.apache.kafka.clients.admin.AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG;
import static org.apache.kafka.clients.admin.AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG;
import static org.apache.kafka.common.internals.Topic.GROUP_METADATA_TOPIC_NAME;
#Slf4j
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {TopicService.class})
public class TopicServiceTest {
#Autowired
TopicService topicService;
#MockBean
AdminClient adminClient;
ListTopicsResult listTopicsResult;
KafkaFuture<Set<String>> future;
NewTopic newTopic;
Topic topic;
Collection<NewTopic> topicList;
CreateTopicsResult createTopicsResult;
Void t;
Map<String,KafkaFuture<Void>> futureMap;
private static final String TARGET_CONSUMER_GROUP_ID = "target-group-id";
private static final Map<String, Object> CONF = new HashMap<>();
#BeforeClass
public static void createAdminClient() {
try {
CONF.put(BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
CONF.put(REQUEST_TIMEOUT_MS_CONFIG, 120000);
CONF.put("zookeeper.connect", "localhost:21891");
AdminClient adminClient = AdminClient.create(CONF);
} catch (Exception e) {
throw new RuntimeException("create kafka admin client error", e);
}
}
#Before
public void setUp(){
topicList = new ArrayList<>();
newTopic = new NewTopic("topic-7",1, (short) 1);
topicList.add(newTopic);
futureMap = new HashMap<>();
topic = new Topic();
topic.setName("topic-1");
}
#Test
public void createTopic() throws ExecutionException, InterruptedException {
Properties consumerProperties = new Properties();
Mockito.when(adminClient.createTopics(topicList))
.thenReturn(Mockito.mock(CreateTopicsResult.class));
Mockito.when(adminClient.createTopics(topicList).values())
.thenReturn(Mockito.mock(Map.class));
Mockito.when(adminClient.createTopics(topicList)
.values()
.get(GROUP_METADATA_TOPIC_NAME)).thenReturn(Mockito.mock(KafkaFutureImpl.class));
Mockito.when(adminClient.createTopics(topicList)
.values()
.get(GROUP_METADATA_TOPIC_NAME)
.get()).thenReturn(t);
topicService.createTopic(topic);
}
}
package org.kafka.config;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.kafka.reader.Kafka;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.core.KafkaAdmin;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
#Component
public class AdminConfigurer {
#Autowired
private Kafka kafkaConfig;
#Bean
public Map<String, Object> kafkaAdminProperties() {
final Map<String, Object> configs = new HashMap<>();
configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServers());
if(kafkaConfig.getProperties().getSasl().getEnabled() && kafkaConfig.getSsl().getEnabled()) {
configs.put("sasl.mechanism", kafkaConfig.getProperties().getSasl().getMechanism());
configs.put("security.protocol", kafkaConfig.getProperties().getSasl().getSecurity().getProtocol());
configs.put("ssl.keystore.location", kafkaConfig.getSsl().getKeystoreLocation());
configs.put("ssl.keystore.password", kafkaConfig.getSsl().getKeystorePassword());
configs.put("ssl.truststore.location", kafkaConfig.getSsl().getTruststoreLocation());
configs.put("ssl.truststore.password", kafkaConfig.getSsl().getTruststorePassword());
configs.put("sasl.jaas.config", String.format(kafkaConfig.getJaasTemplate(),
kafkaConfig.getProperties().getSasl().getJaas().getConfig().getUsername(),
kafkaConfig.getProperties().getSasl().getJaas().getConfig().getPassword()));
configs.put("ssl.endpoint.identification.algorithm", "");
}
return configs;
}
#Bean
public AdminClient getClient() {
return AdminClient.create(kafkaAdminProperties());
}
}
I expected the below test case run successfully. But i'm getting below error.
java.lang.NullPointerException
at org.kafka.service.TopicService.createTopic(TopicService.java:57)
at org.kafka.service.TopicServiceTest.createTopic(TopicServiceTest.java:100)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
I'm using 2.7.1 spring version with following client dependency.
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.1.1</version>
<scope>test</scope>
<classifier>test</classifier>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
Related
I have one micro-service named order-service in spring boot.
:: Spring Boot :: (v2.0.5.RELEASE)
in that micro-service, in service layer I have 2 service class 1. OrderManagerService and OrderService. From OrderService I call Repository Interface IOrderRequestRepository which extends PagingAndSortingRepository interface. so The call is basically OrderManagerService -> OrderService -> IOrderRequestRepository.
Right now I am using one data source for the micro-service. Now my requirement is I have to use 2 data source because in OrderManagerService there is one method getCustomerVisitsForMonth(Long customerID) which frequently used and we don't want to give load on prod DB server. Instead we will use prod-replica server for this one particular method.
I have googled a lot of blogs and tried to implement but ending up getting different exception.
below are exceptions:
could not execute query; SQL [SELECT * FROM ....(this is the actual query)]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute query",
Could not open JPA EntityManager for transaction; nested exception is org.hibernate.TransactionException: JDBC begin transaction failed:
Below are my sample code:
application-env.properties
spring.datasource.url=jdbc:mysql://someurl:3306/orders?zeroDateTimeBehavior=convertToNull&useSSL=false
spring.datasource.username=<username>
spring.datasource.password=<password>
spring.datasource.driverClassName=com.mysql.jdbc.Driver
#replica datasource details
spring.replica.datasource.url=jdbc:mysql://someurl:3306/order?zeroDateTimeBehavior=convertToNull&useSSL=false
spring.replica.datasource.username=<username>
spring.replica.datasource.password=<password>
spring.replica.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.tomcat.initial-size=15
spring.datasource.tomcat.max-wait=20000
spring.datasource.tomcat.max-active=5
spring.datasource.tomcat.max-idle=4
spring.datasource.tomcat.min-idle=2
spring.datasource.tomcat.default-auto-commit=true
#replica data source details
spring.replica.datasource.tomcat.initial-size=15
spring.replica.datasource.tomcat.max-wait=20000
spring.replica.datasource.tomcat.max-active=5
spring.replica.datasource.tomcat.max-idle=4
spring.replica.datasource.tomcat.min-idle=2
spring.replica.datasource.tomcat.default-auto-commit=true
Main class
package in.demo.order.web.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#SpringBootApplication(scanBasePackages = "in.demo.order",
exclude = { SecurityAutoConfiguration.class })
public class OrderServiceApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(OrderServiceApplication.class);
}
}
I have created 2 different packages for Repository classes and for Entity classes, however my Entity and Repository are same.
packages
in.demo.order.dao
in.demo.order.repodao
Configuration classes
MainDataSourceConfiguration
package in.demo.order.web.config;
import in.demo.order.replicadao.IOrderRequestRepositoryForReplica;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = "in.demo.order.dao",
entityManagerFactoryRef = "mainEntityManagerFactory",
transactionManagerRef = "mainTransactionManager"
)
public class MainDataSourceConfiguration {
#Autowired
private Environment environment;
#Primary
#Bean(name = "mainDataSourceProperties")
#ConfigurationProperties("spring.datasource")
public DataSourceProperties mainDataSourceProperties() {
return new DataSourceProperties();
}
#Primary
#Bean(name = "mainDataSource")
#ConfigurationProperties("spring.datasource.configuration")
public DataSource mainDataSource(
#Qualifier("mainDataSourceProperties") DataSourceProperties mainDataSourceProperties) {
return mainDataSourceProperties
.initializeDataSourceBuilder()
.type(org.apache.tomcat.jdbc.pool.DataSource.class)
.build();
}
#Primary
#Bean(name = "mainEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean mainEntityManagerFactory(
EntityManagerFactoryBuilder mainEntityManagerFactoryBuilder,
#Qualifier("mainDataSource") DataSource mainDataSource) {
Map<String, String> mainJpaProperties = new HashMap<>();
mainJpaProperties.put("hibernate.dialect",
environment.getProperty("spring.jpa.properties.hibernate.dialect"));
mainJpaProperties.put("hibernate.hbm2ddl.auto",
environment.getProperty("spring.jpa.hibernate.ddl-auto"));
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = mainEntityManagerFactoryBuilder
.dataSource(mainDataSource)
.packages("in.demo.order.dao.model")
.persistenceUnit("mainDataSource")
.properties(mainJpaProperties)
.build();
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return localContainerEntityManagerFactoryBean;
}
#Primary
#Bean(name = "mainTransactionManager")
public PlatformTransactionManager mainTransactionManager(
#Qualifier("mainEntityManagerFactory") EntityManagerFactory mainEntityManagerFactory) {
return new JpaTransactionManager(mainEntityManagerFactory);
}
}
ReplicaDataSourceConfiguration
package in.demo.order.web.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
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.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = "in.demo.order.replicadao",
entityManagerFactoryRef = "replicaEntityManagerFactory",
transactionManagerRef = "replicaTransactionManager"
)
public class ReplicaDataSourceConfiguration {
#Autowired
private Environment environment;
#Bean(name = "replicaDataSourceProperties")
#ConfigurationProperties("spring.replica.datasource")
public DataSourceProperties replicaDataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "replicaDataSource")
#ConfigurationProperties("spring.replica.datasource.configuration")
public DataSource replicaDataSource(
#Qualifier("replicaDataSourceProperties") DataSourceProperties replicaDataSourceProperties) {
return replicaDataSourceProperties
.initializeDataSourceBuilder()
.type(org.apache.tomcat.jdbc.pool.DataSource.class)
.build();
}
#Bean(name = "replicaEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean replicaEntityManagerFactory(
EntityManagerFactoryBuilder replicaEntityManagerFactoryBuilder,
#Qualifier("replicaDataSource") DataSource replicaDataSource) {
Map<String, String> replicaJpaProperties = new HashMap<>();
replicaJpaProperties.put("hibernate.dialect",
environment.getProperty("spring.jpa.properties.hibernate.dialect"));
replicaJpaProperties.put("hibernate.hbm2ddl.auto",
environment.getProperty("spring.jpa.hibernate.ddl-auto"));
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = replicaEntityManagerFactoryBuilder
.dataSource(replicaDataSource)
.packages("in.demo.order.replicadao.model")
.persistenceUnit("replicaDataSource")
.properties(replicaJpaProperties)
.build();
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return localContainerEntityManagerFactoryBean;
}
#Bean(name = "replicaTransactionManager")
public PlatformTransactionManager replicaTransactionManager(
#Qualifier("replicaEntityManagerFactory") EntityManagerFactory replicaEntityManagerFactory) {
return new JpaTransactionManager(replicaEntityManagerFactory);
}
}
OrderManagerService
package in.demo.order.service;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
#Transactional
public class OrderManagerService{
#Autowired
private OrderService orderService;
public CustomerVisitsResponse getCustomerVisitsForMonthForReplicaDataSource(Long customerId) {
return orderService.getTotalCustomerVisitsForMonth(customerId);
}
public CustomerVisitsResponse getCustomerVisitsForMonthForMainDataSource(Long customerId) {
return orderService.getTotalCustomerVisitsForMonthForMainDataSource(customerId);
}
}
OrderService
package in.demo.order.service;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import in.demo.order.replicadao.IOrderRequestRepositoryForReplica;
import in.demo.order.dao.IOrderRequestRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
#Transactional(rollbackFor = Exception.class)
public class OrderService {
#Autowired
private IOrderRequestRepositoryForMain orderRequestRepositoryForMain;
#Autowired
private IOrderRequestRepositoryForReplica orderRequestRepositoryForReplica;
public CustomerVisitsResponse getTotalCustomerVisitsForMonthForReplicaDataSource(Long customerId) {
List<Integer> statusIdList= new ArrayList<>();
CustomerVisitsResponse response= new CustomerVisitsResponse();
statusIdList.add(OrderServiceConstants.SOME_CONSTANT_VALUE);
List<Integer> sellerIdList= new ArrayList<>();
sellerIdList.add(OrderServiceConstants.SOME_CONSTANT_VALUE);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM");
Date date = new Date();
String month= dateFormat.format(date)+"%";
try {
List<in.demo.order.replicadao.model.OrderEntity> orders =
orderRequestRepositoryForReplica.getOrderBycustomerIdAndBdMonth(customerId,statusIdList,month,sellerIdList);
if(orders!=null && !orders.isEmpty())
{
double total=0d;
for(in.demo.order.replicadao.model.OrderEntity order:orders)
{
total+=order.getPayableAmount();
}
response.setTotalSpent(total);
response.setTotalVisits((long)(orders.size()));
}
else {
response.setTotalSpent(0d);
response.setTotalVisits(0l);
}
} catch (Exception e) {
logger.info("Error occured while fetching getTotalCustomerVisitsForMonth customerid: " + customerId);
response.setTotalSpent(0d);
response.setTotalVisits(0l);
return response;
}
return response;
}
public CustomerVisitsResponse getTotalCustomerVisitsForMonthForMainDataSource(Long customerId) {
List<Integer> statusIdList= new ArrayList<>();
CustomerVisitsResponse response= new CustomerVisitsResponse();
statusIdList.add(OrderServiceConstants.SOME_CONSTANT_VALUE);
List<Integer> sellerIdList= new ArrayList<>();
sellerIdList.add(OrderServiceConstants.SOME_CONSTANT_VALUE);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM");
Date date = new Date();
String month= dateFormat.format(date)+"%";
try {
List<in.demo.order.dao.model.OrderEntity> orders =
orderRequestRepositoryForMain.getOrderBycustomerIdAndBdMonth(customerId,statusIdList,month,sellerIdList);
if(orders!=null && !orders.isEmpty())
{
double total=0d;
for(in.demo.order.dao.model.OrderEntity order:orders)
{
total+=order.getPayableAmount();
}
response.setTotalSpent(total);
response.setTotalVisits((long)(orders.size()));
}
else {
response.setTotalSpent(0d);
response.setTotalVisits(0l);
}
} catch (Exception e) {
logger.info("Error occured while fetching getTotalCustomerVisitsForMonth customerid: " + customerId);
response.setTotalSpent(0d);
response.setTotalVisits(0l);
return response;
}
return response;
}
}
Repository classes
package in.demo.order.replicadao;
IOrderRequestRepositoryForReplica
package in.demo.order.replicadao;
import in.demo.order.replicadao.model.OrderEntity;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public interface IOrderRequestRepositoryForReplica extends PagingAndSortingRepository<OrderEntity, Long> {
#Query(nativeQuery = true,
value = "SELECT * FROM orders ...<some query>")
public List<OrderEntity> getOrderBycustomerIdAndBdMonth(
Long customerId, List<Integer> statusIdList, String month, List<Integer> sellerIdList);
}
package in.demo.order.dao;
IOrderRequestRepositoryForMain
package in.demo.order.dao;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import in.demo.order.dao.model.OrderEntity;
#Repository
public interface IOrderRequestRepositoryForMain extends PagingAndSortingRepository<OrderEntity, Long> {
#Query(nativeQuery = true,
value = "SELECT * FROM orders ...<some query>")
public List<OrderEntity> getOrderBycustomerIdAndBdMonth(Long customerId,
List<Integer> statusIdList, String month, List<Integer> sellerIdList);
}
Same Entity in different package with different serialVersionUID
packages are:
package in.demo.order.replicadao.model;
package in.demo.order.dao.model;
OrderEntity
package in.demo.order.replicadao.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "orders")
public class OrderEntity implements Serializable {
/**
*
*/
private static final long serialVersionUID = 4667868888563693990L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String orderId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
}
Controller class
OrderController
package in.demo.order.controller;
import java.util.List;
import javax.sql.DataSource;
import in.demo.order.service.OrderManagerService;
#RestController
#RequestMapping("/api/order")
public class OrderController {
#Autowired
private OrderManagerService orderManagerService;
#Autowired
private DataSource mainDataSource;
#Autowired
#Qualifier("replicaDataSource")
private DataSource replicaDataSource;
#GetMapping("/replica")
public CustomerVisitsResponse getCustomerVisitResponseForReplica() {
Long customerId = 123L;
return orderManagerService.getCustomerVisitsForMonthForReplicaDataSource(customerId);
}
#GetMapping("/main")
public CustomerVisitsResponse getCustomerVisitResponseForMain() {
Long customerId = 123L;
return orderManagerService.getCustomerVisitsForMonthForMainDataSource(customerId);
}
#GetMapping("/maindatasource")
public String getMainDataSource() {
return mainDataSource.toString();
}
#GetMapping("/replicadatasource")
public String getReplicaDataSource() {
return replicaDataSource.toString();
}
}
I have checked manually in both Database for the query I am running. Data is present. But when I am running from postman to test
for replica, I am getting
Method threw 'org.springframework.dao.InvalidDataAccessResourceUsageException' exception. caused by org.hibernate.exception.SQLGrammarException: could not execute query
for main, I am getting
Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit
When I am using one datasource, the query is working fine.
The issue was with Entity class column name mapping issue, previously we were not mentioning #Column(name="column_name") , once I added the annotation, it started to work. But still I am confused how it is working fine when using single Data source
For the following test I am always getting the error:
org.opentest4j.AssertionFailedError: expected: 10 but was : 0
What exactly I am trying to verify in scope of the test:
I am trying to send 10 messages to Kafka and after that immediately I am trying to read those messages from Kafka, but for some unknown reason KafkaConsumer returns 0 records, and I am struggling to understand why Consumer can't read messages that were sent earlier?
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.stream.IntStream;
#SpringBootTest
#ActiveProfiles("test")
#ContextConfiguration(initializers = TestKafkaContextInitializer.class)
#Slf4j
public class KafkaFlowVerificationITest {
#Autowired
private KafkaTemplate<String, String> kafkaTemplate;
#Autowired
private KafkaProperties kafkaProperties;
private final String kafkaTopic = "test.topic";
#Test
void testKafkaFlow() {
IntStream.range(0, 10)
.forEach(e -> {
try {
kafkaTemplate.send(kafkaTopic, UUID.randomUUID().toString()).get();
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
});
checkKafkaForMessage();
}
private void checkKafkaForMessage() {
Map<String, Object> properties = new HashMap<>();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getBootstrapServers());
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, org.apache.kafka.common.serialization.StringDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, org.apache.kafka.common.serialization.StringDeserializer.class);
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "acme");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
consumer.subscribe(List.of(kafkaTopic));
ConsumerRecords<String, String> records = consumer.poll(Duration.ZERO);
Assertions.assertThat(records.count()).isEqualTo(10);
}
}
and TestKafkaContextInitializer:
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.testcontainers.containers.KafkaContainer;
import org.testcontainers.utility.DockerImageName;
#Slf4j
public class TestKafkaContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final KafkaContainer kafkaContainer =
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:5.4.3"));
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
kafkaContainer.start();
var values = TestPropertyValues.of(
"spring.kafka.producer.bootstrap-servers=" + kafkaContainer.getBootstrapServers(),
"spring.kafka.consumer.bootstrap-servers=" + kafkaContainer.getBootstrapServers()
);
values.applyTo(configurableApplicationContext);
}
}
The root cause of the issue is:
I set TestContainer Kafka bootstrap server url for the following properties:
spring.kafka.producer.bootstrap-servers
spring.kafka.consumer.bootstrap-servers
but in the test I use:
spring.kafka.bootstrap-servers property, that why Consumer made attempt connect to localhost:9092 default URL instead of URL provided by TestContainer.
I am new to Junit. I am literally struggling to write Junit test cases for my code. I am working with Spring boot, Batch and JPA. I configured Jobs,file read and write in to DB everything working fine. But when it comes to JUnit, I don't even get an idea to write code. Can anyone help me. Below is my code
FileProcessController.java
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.sc.batchservice.model.StatusResponse;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#RestController
#RequestMapping("/fileProcess")
public class FileProcessController {
#Autowired
private JobLauncher jobLauncher;
#Autowired
#Qualifier("euronetFileParseJob")
private Job job;
#GetMapping(path = "/process")
public #ResponseBody StatusResponse process() throws Exception {
try {
Map<String, JobParameter> parameters = new HashMap<>();
parameters.put("date", new JobParameter(new Date()));
jobLauncher.run(job, new JobParameters(parameters));
return new StatusResponse(true);
} catch (Exception e) {
log.error("Exception", e);
Throwable rootException = ExceptionUtils.getRootCause(e);
String errMessage = rootException.getMessage();
log.info("Root cause is instance of JobInstanceAlreadyCompleteException --> "+(rootException instanceof JobInstanceAlreadyCompleteException));
if(rootException instanceof JobInstanceAlreadyCompleteException){
log.info(errMessage);
return new StatusResponse(false, "This job has been completed already!");
} else{
throw e;
}
}
}
}
BatchConfig.java
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import com.sc.batchservice.model.DetailsDTO;
#Configuration
#EnableBatchProcessing
public class BatchConfig {
private JobBuilderFactory jobBuilderFactory;
#Autowired
public void setJobBuilderFactory(JobBuilderFactory jobBuilderFactory) {
this.jobBuilderFactory = jobBuilderFactory;
}
#Autowired
StepBuilderFactory stepBuilderFactory;
#Value("file:${input.files.location}${input.file.pattern}")
private Resource[] fileInputs;
#Value("${euronet.file.column.names}")
private String filecolumnNames;
#Value("${euronet.file.column.lengths}")
private String fileColumnLengths;
#Value("${input.files.location}")
private String inputFileLocation;
#Value("${input.file.pattern}")
private String inputFilePattern;
#Autowired
FlatFileWriter flatFileWriter;
#Bean
public Job euronetFileParseJob() {
return jobBuilderFactory.get("euronetFileParseJob")
.incrementer(new RunIdIncrementer())
.start(fileStep())
.build();
}
public Step fileStep() {
return stepBuilderFactory.get("fileStep")
.<DetailsDTO, DetailsDTO>chunk(10)
.reader(new FlatFileReader(fileInputs, filecolumnNames, fileColumnLengths))
.writer(flatFileWriter)
.build();
}
}
FlatFileReader.java
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.MultiResourceItemReader;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FixedLengthTokenizer;
import org.springframework.core.io.Resource;
import com.sc.batchservice.model.DetailsDTO;
import com.sc.batchservice.util.CommonUtil;
import lombok.extern.slf4j.Slf4j;
#Slf4j
public class FlatFileReader extends MultiResourceItemReader<DetailsDTO> {
public EuronetFlatFileReader(Resource[] fileInputs, String filecolumnNames, String fileColumnLengths) {
setResources(fileInputs);
setDelegate(reader(filecolumnNames, fileColumnLengths));
setStrict(true);
}
private FlatFileItemReader<DetailsDTO> reader(String filecolumnNames, String fileColumnLengths) {
FlatFileItemReader<DetailsDTO> flatFileItemReader = new FlatFileItemReader<>();
FixedLengthTokenizer tokenizer = CommonUtil.fixedLengthTokenizer(filecolumnNames, fileColumnLengths);
FieldSetMapper<DetailsDTO> mapper = createMapper();
DefaultLineMapper<DetailsDTO> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(tokenizer);
lineMapper.setFieldSetMapper(mapper);
flatFileItemReader.setLineMapper(lineMapper);
return flatFileItemReader;
}
/*
* Mapping column data to DTO
*/
private FieldSetMapper<DetailsDTO> createMapper() {
BeanWrapperFieldSetMapper<DetailsDTO> mapper = new BeanWrapperFieldSetMapper<>();
try {
mapper.setTargetType(DetailsDTO.class);
} catch(Exception e) {
log.error("Exception in mapping column data to dto ", e);
}
return mapper;
}
}
I have Writer,Entity and model classes also, But if any example or idea upto this code, I can proceed with those classes.
We have a spring boot application and have written test cases for the service class. The test case is running when I am running it as Right Click -> Run As -> JUnit Test. But when I am running it through maven, it is not found. My test class is as below:
package dk.tdc.fasapi.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import dk.tdc.fasapi.repository.implementation.jdbc.IncidentDetailsRepo;
import dk.tdc.fasapi.requestResponse.incidents.getdetails.IncidentDetails;
#RunWith(MockitoJUnitRunner.class)
public class TestIncidentDetailsService {
private Map<String, Object> fasoDetailsMap = null;
private List<Map<String, Object>> subStateMapList = null;
#Mock
private IncidentDetailsRepo incidentDetailsRepo;
#InjectMocks
private IncidentDetailsService incidentDetailsService;
#Before
public void beforeMethod() {
fasoDetailsMap = new HashMap<>();
fasoDetailsMap.put("FaultDescription", "Test Description");
fasoDetailsMap.put("UrgentCode", "01");
fasoDetailsMap.put("LID", "12345678");
fasoDetailsMap.put("CustomerName", "TDC");
fasoDetailsMap.put("CustomerPhone", "11223344");
fasoDetailsMap.put("ExtRefId", "1234");
fasoDetailsMap.put("SSID", "0001");
Map<String, Object> subStateMap = new HashMap<String, Object>();
subStateMap.put("X_CURRENT_STATUS", "Accepted");
subStateMapList = new ArrayList<Map<String,Object>>();
subStateMapList.add(subStateMap);
}
#Test
public void testExecuteOpenFaso() throws Exception {
Mockito.when(incidentDetailsRepo.getOpenFasoDetails(Mockito.anyString())).thenReturn(fasoDetailsMap);
Mockito.when(incidentDetailsRepo.getCurrentStatus(Mockito.anyString())).thenReturn(subStateMapList);
IncidentDetails incidentDetails = incidentDetailsService.execute("20181225-000001");
Assert.assertEquals(incidentDetails.getId(), "20181225-000001");
Assert.assertEquals(incidentDetails.getStatus(), "Open");
Assert.assertEquals(incidentDetails.getTitle(), "Test Description");
Assert.assertEquals(incidentDetails.getSeverity(), "01");
Assert.assertEquals(incidentDetails.getParameter().getType(), "LID");
Assert.assertEquals(incidentDetails.getParameter().getValue(), "12345678");
Assert.assertEquals(incidentDetails.getContact().getName(), "TDC");
Assert.assertEquals(incidentDetails.getContact().getNumber(), "11223344");
Assert.assertEquals(incidentDetails.getReferences().getTicket(), "1234");
Assert.assertEquals(incidentDetails.getReferences().getSsid(), "0001");
Assert.assertEquals(incidentDetails.getReferences().getContractor(), "ENIIG180619");
Assert.assertEquals(incidentDetails.getSubState(), "Accepted");
}
#Test
public void testExecuteClosedFaso() throws Exception {
Mockito.when(incidentDetailsRepo.getOpenFasoDetails(Mockito.anyString())).thenReturn(null);
Mockito.when(incidentDetailsRepo.getClosedFasoDetails(Mockito.anyString())).thenReturn(fasoDetailsMap);
Mockito.when(incidentDetailsRepo.getCurrentStatus(Mockito.anyString())).thenReturn(subStateMapList);
IncidentDetails incidentDetails = incidentDetailsService.execute("20181225-000001");
Assert.assertEquals(incidentDetails.getId(), "20181225-000001");
Assert.assertEquals(incidentDetails.getStatus(), "Closed");
Assert.assertEquals(incidentDetails.getTitle(), "Test Description");
Assert.assertEquals(incidentDetails.getSeverity(), "01");
Assert.assertEquals(incidentDetails.getParameter().getType(), "LID");
Assert.assertEquals(incidentDetails.getParameter().getValue(), "12345678");
Assert.assertEquals(incidentDetails.getContact().getName(), "TDC");
Assert.assertEquals(incidentDetails.getContact().getNumber(), "11223344");
Assert.assertEquals(incidentDetails.getReferences().getTicket(), "1234");
Assert.assertEquals(incidentDetails.getReferences().getSsid(), "0001");
Assert.assertEquals(incidentDetails.getReferences().getContractor(), "ENIIG180619");
Assert.assertEquals(incidentDetails.getSubState(), "Accepted");
}
#After
public void afterMethod() {
fasoDetailsMap = null;
}
}
Can anyone please suggest whether I will have to add any more annotations on my test class to make it visible?
I'm trying to configure Spring Boot + MyBatis application which should work with several datasources. I tried to do it similar to sample here.
Querying and updating data is working, but when I wrote the unit test, I found that #Transactional is not working. Transactions must work between all databases. It means that, if one method with #Transactional make updates on both databases then everything should rollback in case of exception.
It is a sample application for testing purposes and co-workers. After successful configuring the new applications will be configured and developed in similar manner.
Maven:
<?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.aze.mybatis</groupId>
<artifactId>sample-one</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Below are the configuration classes:
package com.aze.mybatis.sampleone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
#SpringBootApplication
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Config to database BSCS (Oracle)
package com.aze.mybatis.sampleone.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
#Configuration
#MapperScan(basePackages = "com.aze.mybatis.sampleone.dao", annotationClass = BscsDataSource.class, sqlSessionFactoryRef = BscsDatabaseConfig.SQL_SESSION_FACTORY_NAME)
#EnableTransactionManagement
public class BscsDatabaseConfig {
static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryBscs";
private static final String TX_MANAGER = "txManagerBscs";
#Bean(name = "dataSourceBscs")
#ConfigurationProperties(prefix = "bscs.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = TX_MANAGER)
public PlatformTransactionManager txManagerBscs() {
return new DataSourceTransactionManager(dataSource());
}
#Bean(name = BscsDatabaseConfig.SQL_SESSION_FACTORY_NAME)
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean.getObject();
}
}
Config to database ONSUBS (Oracle)
package com.aze.mybatis.sampleone.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
#Configuration
#MapperScan(basePackages = "com.aze.mybatis.sampleone.dao", annotationClass = OnsubsDataSource.class, sqlSessionFactoryRef = OnsubsDatabaseConfig.SQL_SESSION_FACTORY_NAME)
#EnableTransactionManagement
public class OnsubsDatabaseConfig {
static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryOnsubs";
private static final String TX_MANAGER = "txManagerOnsubs";
#Bean(name = "dataSourceOnsubs")
#Primary
#ConfigurationProperties(prefix = "onsubs.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = TX_MANAGER)
#Primary
public PlatformTransactionManager txManagerOnsubs() {
return new DataSourceTransactionManager(dataSource());
}
#Bean(name = OnsubsDatabaseConfig.SQL_SESSION_FACTORY_NAME)
#Primary
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean.getObject();
}
}
Annotation for BSCS:
package com.aze.mybatis.sampleone.config;
public #interface BscsDataSource {
}
and ONSUBS:
package com.aze.mybatis.sampleone.config;
public #interface OnsubsDataSource {
}
Mapper interface that should work with ONSUBS:
package com.aze.mybatis.sampleone.dao;
import com.aze.mybatis.sampleone.config.OnsubsDataSource;
import com.aze.mybatis.sampleone.domain.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
#Mapper
#OnsubsDataSource
public interface PaymentDao {
Payment getPaymentById(#Param("paymentId") Integer paymentId);
}
and BSCS:
package com.aze.mybatis.sampleone.dao;
import com.aze.mybatis.sampleone.config.BscsDataSource;
import com.aze.mybatis.sampleone.domain.PostpaidBalance;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
#Mapper
#BscsDataSource
public interface PostpaidCustomerDao {
PostpaidBalance getPostpaidBalance(#Param("customerId") Integer customerId);
// BigDecimal amount may be used as second parameter, but I want to show, how to work with two parameters where second is object
void updateDepositAmount(#Param("customerId") Integer customerId, #Param("balance") PostpaidBalance postpaidBalance);
void updateAzFdlLastModUser(#Param("customerId") Integer customerId, #Param("username") String username);
}
Below is a code with #Transactional
package com.aze.mybatis.sampleone.service;
import com.aze.mybatis.sampleone.dao.PaymentDao;
import com.aze.mybatis.sampleone.dao.PostpaidCustomerDao;
import com.aze.mybatis.sampleone.domain.Payment;
import com.aze.mybatis.sampleone.domain.PostpaidBalance;
import com.aze.mybatis.sampleone.exception.DataNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
#Service
public class PaymentServiceImpl implements PaymentService {
private static final String MIN_DEPOSIT_AMOUNT = "150";
#Autowired
private PaymentDao paymentDao;
#Autowired
private PostpaidCustomerDao postpaidCustomerDao;
#Override
public PostpaidBalance getPostpaidBalance(Integer customerId) {
PostpaidBalance balance = postpaidCustomerDao.getPostpaidBalance(customerId);
if (balance == null) {
throw new DataNotFoundException(String.format("Can't find any balance information for customer with customer_id = %d", customerId));
}
return balance;
}
// Note. By default rolling back on RuntimeException and Error but not on checked exceptions
// If you want to rollback on check exception too then add "rollbackFor = Exception.class"
#Transactional(rollbackFor = Exception.class)
#Override
public void updateDepositAmount(Integer customerId, PostpaidBalance postpaidBalance, String username) {
postpaidCustomerDao.updateDepositAmount(customerId, postpaidBalance);
// In case of #Transactional annotation, you can use method from the same class if it doesn't change data on database
PostpaidBalance balance = getPostpaidBalance(customerId);
// This logic is for showing that how the #Transactional annotation works.
// Because of the exception, the previous transaction will rollback
if (balance.getDeposit().compareTo(new BigDecimal(MIN_DEPOSIT_AMOUNT)) == -1) {
throw new IllegalArgumentException("The customer can not have deposit less than " + MIN_DEPOSIT_AMOUNT);
}
// In case of #Transactional annotation, you must not (!!!) use method from the same (!) class if it changes data on database
// That is why, postpaidCustomerDao.updateAzFdlLastModUser() used here instead of this.updateAzFdlLastModUser()
postpaidCustomerDao.updateAzFdlLastModUser(customerId, username);
// If there is no exception, the transaction will commit
}
}
Below is the unit test code:
package com.aze.mybatis.sampleone.service;
import com.aze.mybatis.sampleone.Application;
import com.aze.mybatis.sampleone.config.BscsDatabaseConfig;
import com.aze.mybatis.sampleone.config.OnsubsDatabaseConfig;
import com.aze.mybatis.sampleone.domain.PostpaidBalance;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.math.BigDecimal;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Application.class, OnsubsDatabaseConfig.class, BscsDatabaseConfig.class})
#TestPropertySource(locations= "classpath:application.properties")
public class PaymentServiceImplTest extends Assert {
// My goal is not to write a full and right unit tests, but just show you examples of working with MyBatis
#Autowired
private PaymentService paymentService;
#Before
public void setUp() throws Exception {
assert paymentService != null;
}
#Test
public void updateDepositAmount() throws Exception {
final int customerId = 4301887; // not recommended way. Just for sample
final String username = "ITCSC";
boolean exceptionRaised = false;
PostpaidBalance balance = paymentService.getPostpaidBalance(customerId);
assertTrue("Find customer with deposit = 0", balance.getDeposit().compareTo(BigDecimal.ZERO) == 0);
balance.setDeposit(BigDecimal.TEN);
try {
paymentService.updateDepositAmount(customerId, balance, username);
} catch (Exception e) {
exceptionRaised = true;
}
assertTrue(exceptionRaised);
balance = paymentService.getPostpaidBalance(customerId);
// We check that transaction was rollback and amount was not changed
assertTrue(balance.getDeposit().compareTo(BigDecimal.ZERO) == 0);
final BigDecimal minDepositAmount = new BigDecimal("150");
balance.setDeposit(minDepositAmount);
paymentService.updateDepositAmount(customerId, balance, username);
balance = paymentService.getPostpaidBalance(customerId);
assertTrue(balance.getDeposit().compareTo(minDepositAmount) != -1);
}
}
Unit test fails on assertTrue(balance.getDeposit().compareTo(BigDecimal.ZERO) == 0);. The I check the database and see that first update postpaidCustomerDao.updateDepositAmount(customerId, postpaidBalance); was not rollback despite the #Transactional annotation.
Please help to solve problem.
If you have multiple TransactionManagers, you'll need to reference the one you want to use for #Transactional using Bean names or Qualifiers.
In your Java Config:
#Bean("myTM")
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(myDatasource());
}
In Services:
#Transactional("myTM")
public void insertWithException(JdbcTemplate jdbcTemplate) {
}
I debug'd my app to see how Spring chooses the TransactionManager. This is done in org.springframework.transaction.interceptor.TransactionAspectSupport#determineTransactionManager
/**
* Determine the specific transaction manager to use for the given transaction.
*/
protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) {
// Do not attempt to lookup tx manager if no tx attributes are set
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(qualifier);
}
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.transactionManagerBeanName);
}
else {
PlatformTransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}
So it's just getting the default primary PlatformTransactionManager bean.