I am trying to run tests on a controller class and it has atleast a method which internally uses a DAO to retrieve information from the DB (MySQL).
The problem I have is that the dao method is giving null and I end up with NullPointerException error.
How do I tests a class which has methods that internally use a database connection?
Haven't been able to find any useful post/answer.
Project structure:
src
main
java
[package]
RegisterController.java
Config.java // configuration class
test
java
[package]
RegisterControllerTest.java
RegisterController.java
package com.webapp.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.webapp.Config;
import com.webapp.dao.AccountDao;
#Controller
public class RegisterController {
#Autowired
AccountDao accDao;
public String validateUsername(String uname){
List<String> errors = new ArrayList<String>();
// ... unrelated code
// NullPointerException thrown here
if(accDao.getAccountByUsername(uname) != null)
errors.add("err#taken");
return errors.toString();
}
}
RegisterControllerTest.java
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import com.webapp.controller.RegisterController;
import com.webapp.Config;
#SpringBootTest
public class RegisterControllerTest {
#Mock
private Config config;
private RegisterController rc;
#BeforeEach
public void init() {
config = Mockito.mock(Config.class);
rc = new RegisterController();
}
#Test
public void testValidateUsername() {
assertEquals("[]", rc.validateUsername("Username123")); // N.P.E
}
}
Config.java
package com.webapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import com.webapp.dao.AccountDao;
import com.webapp.dao.AccountDaoImpl;
import com.webapp.dao.Dao;
#Configuration
#ComponentScan(basePackages = { "com.webapp.controller", "com.webapp.dao", "com.webapp.test" })
public class Config {
private static class Database {
private static String host = "127.0.0.1";
private static String user = "root";
private static String pass = "root";
private static String dbname = "memedb";
private static int port = 3306;
public static String getUrl() {
return "jdbc:mysql://"+host+":"+port+"/"+dbname+"?serverTimezone=Europe/Stockholm";
}
}
#Bean
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl(Database.getUrl());
ds.setUsername(Database.user);
ds.setPassword(Database.pass);
return ds;
}
#Bean
public AccountDao getAccDao() {
return new AccountDaoImpl(getDataSource());
}
}
Instead of mocking config, mock the Dao. You are getting NPE, because the mocked config is not setting #Autowired accDao... so your accDao == null:
#Controller
public class RegisterController {
AccountDao accDao;
public RegisterController(AccountDao accDao) {
this.accDao = accDao;
}
...
}
#BeforeEach
public void init() {
accDaoMock = Mockito.mock(AccountDao.class);
rc = new RegisterController(accDaoMock);
}
#Test
public void testValidateUsername() {
when(accDaoMock.getAccountByUsername("Username123")).thenReturn(null);
assertEquals("[]", rc.validateUsername("Username123"));
}
Why are you configuring the connection DB programmatically? I advise you configure the connection DB with autoconfiguration of Spring Boot.
The DAO object have to mock in an unit test.
Here is a good article about the Junit test for a Spring Boot App.
Related
I want to use Testcontainers with #DataJpaTest (and #SpringBootTest) using JUnit 5. I have the basic setup working using the #Testcontainers and #Container annotation like this:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Testcontainers
public class AtleteRepositoryTest {
#Container
private static final PostgreSQLContainer<?> CONTAINER = new PostgreSQLContainer<>("postgres:11");
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", CONTAINER::getJdbcUrl);
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}
#Autowired
private AtleteRepository repository;
#Test
void testSave() {
repository.save(new Atlete("Wout Van Aert", 0, 1, 0));
assertThat(repository.count()).isEqualTo(1);
}
}
See https://github.com/wimdeblauwe/blog-example-code/tree/feature/testcontainers-datajpatest/testcontainers-datajpatest for the full example code (AtleteRepositoryTest, TeamRepositoryTest and TestcontainersDatajpatestApplicationTests).
To avoid the repetition of declaring the PostgreSQL container and the dynamic properties, I tried the following:
JUnit 5 extension
Baeldung has a blog about how you can use a JUnit 5 extension to avoid the duplication.
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.testcontainers.containers.PostgreSQLContainer;
public class PostgreSQLExtension implements BeforeAllCallback, AfterAllCallback {
private PostgreSQLContainer<?> postgres;
#Override
public void beforeAll(ExtensionContext context) {
postgres = new PostgreSQLContainer<>("postgres:11");
postgres.start();
System.setProperty("spring.datasource.url", postgres.getJdbcUrl());
System.setProperty("spring.datasource.username", postgres.getUsername());
System.setProperty("spring.datasource.password", postgres.getPassword());
}
#Override
public void afterAll(ExtensionContext context) {
postgres.stop();
}
}
It works if you only have 1 test, but not if you run multiple at the same time (using IntelliJ or with Maven). In that case, one of the tests will fail because there is no connection with the database that can be made.
Also note that this extension does not use the DynamicPropertyRegistry, but plain environment variables.
See the feature/testcontainers-datajpatest_baeldung-extension branch for the code.
Using a common superclass
On the branch feature/testcontainers-datajpatest_database-base-test, I tried using a common superclass:
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
public class DatabaseBaseTest {
private static final PostgreSQLContainer<?> CONTAINER = new PostgreSQLContainer<>("postgres:11");
#BeforeAll
static void start() {
CONTAINER.start();
}
#AfterAll
static void stop() {
CONTAINER.stop();
}
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", () -> {
String jdbcUrl = CONTAINER.getJdbcUrl();
System.out.println("jdbcUrl = " + jdbcUrl);
return jdbcUrl;
});
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}
}
Unfortunately that also does not work. I noticed in the logging that the #DynamicPropertySource annotated method was only called once and not for each test, which led me to try option 3:
Common superclass with #DynamicPropertySource in subclasses
When using the common superclass, but adding the #DynamicPropertySource method in each subclass, it works again.
Example code of such a subclass:
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class AtleteRepositoryTest extends DatabaseBaseTest {
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", () -> {
String jdbcUrl = CONTAINER.getJdbcUrl();
System.out.println("jdbcUrl = " + jdbcUrl);
return jdbcUrl;
});
registry.add("spring.datasource.username", CONTAINER::getUsername);
registry.add("spring.datasource.password", CONTAINER::getPassword);
}
#Autowired
private AtleteRepository repository;
#Test
void testSave() {
repository.save(new Atlete("Wout Van Aert", 0, 1, 0));
assertThat(repository.count()).isEqualTo(1);
}
}
See branch feature/testcontainers-datajpatest_database-base-test_subclasses for that version.
So while it works, there is still a lot of duplication in each test class.
Are there any other options for avoiding the duplication?
To avoid Testcontainers code repetition I generally follow 2 approaches:
Using ApplicationContextInitializer with #ContextConfiguration
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.PostgreSQLContainer;
#Slf4j
public class PostgreSQLContainerInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static PostgreSQLContainer sqlContainer = new PostgreSQLContainer("postgres:10.7");
static {
sqlContainer.start();
}
public void initialize (ConfigurableApplicationContext configurableApplicationContext){
TestPropertyValues.of(
"spring.datasource.url=" + sqlContainer.getJdbcUrl(),
"spring.datasource.username=" + sqlContainer.getUsername(),
"spring.datasource.password=" + sqlContainer.getPassword()
).applyTo(configurableApplicationContext.getEnvironment());
}
}
import com.sivalabs.myservice.common.PostgreSQLContainerInitializer;
import com.sivalabs.myservice.entities.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ContextConfiguration;
import javax.persistence.EntityManager;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
#DataJpaTest
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = {PostgreSQLContainerInitializer.class})
class UserRepositoryTest {
#Autowired
EntityManager entityManager;
#Autowired
private UserRepository userRepository;
#Test
void shouldReturnUserGivenValidCredentials() {
User user = new User(null, "test#gmail.com", "test", "Test");
entityManager.persist(user);
Optional<User> userOptional = userRepository.login("test#gmail.com", "test");
assertThat(userOptional).isNotEmpty();
}
}
Using #DynamicPropertySource in Java 8+ Interface
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
#Testcontainers
public interface PostgreSQLContainerInitializer {
#Container
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:12.3");
#DynamicPropertySource
static void registerPgProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
#DataJpaTest
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest implements PostgreSQLContainerInitializer {
....
....
}
With these approaches we don't have to repeat PostgreSQLContainer declarations and Spring property settings.
Whether to use PostgreSQLContainer as a static field or not depends on whether you want to spin up a new container for every test or 1 container per test class.
PS:
I avoided using common base class approach because sometime one test needs only 1 container and another test needs multiple containers. If we follow add all the containers in common base class then for every test/class all those containers will be started irrespective of their usage which makes tests very slow.
I wrote a JUnit 5 test for my service in my Spring Boot application.
I used #MockBean to mock PasswordEncoder and other beans but I obtain a NullPointerException.
I always obtain a NullPointerException during the when call :
when(compteRepository.getByLogin(anyString())).thenReturn(Optional.of(acc));
Service
package com.compte.application.impl;
import com.compte.application.CompteService;
import com.compte.domain.exceptions.EntityNotFoundExcpetion;
import com.compte.domain.model.Compte;
import com.compte.domain.model.CompteUpdatedData;
import com.compte.domain.repository.CompteRepository;
import com.compte.domain.utils.CompteUtil;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.time.LocalDate;
import java.util.Optional;
/**
* #author mbint
*/
#AllArgsConstructor
public class CompteServiceImpl implements CompteService{
private final static Logger LOGGER = LoggerFactory.getLogger(CompteService.class);
private CompteRepository CompteRepository;
private PasswordEncoder passwordEncoder;
#Override
public Optional<Compte> getByLogin(String login) {
return CompteRepository.getByLogin(login);
}
#Override
public void update(final Long id, CompteUpdatedData updatedData) {
Optional<Compte> optional = CompteRepository.getById(id);
if(optional.isPresent()) {
Compte Compte = optional.get();
Compte.setFirstName(updatedData.getFirstName());
Compte.setLastName(updatedData.getLastName());
CompteRepository.save(Compte);
} else {
throw new EntityNotFoundExcpetion("Compte: " + id + " not found !!");
}
}
}
Junit Test
package com.compte.application;
import com.compte.application.impl.CompteServiceImpl;
import com.compte.domain.model.Compte;
import com.compte.domain.model.CompteUpdatedData;
import com.compte.domain.repository.compteRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static org.mockito.Mockito.*;
/**
* #author mbint
*/
public class CompteServiceImplTest {
private final static String PASSWORD = "Passw00rd";
#MockBean
private compteRepository compteRepository;
#MockBean
private PasswordEncoder passwordEncoder;
private CompteService CompteService = new CompteServiceImpl(compteRepository, passwordEncoder);
#DisplayName(("Should return existing user"))
#Test
private void given_login_then_return_existing_user() {
Compte acc = Compte.builder().id(1L)
.firstName("Luc")
.lastName("JOJO")
.login("xxx#gmail.com")
.password("xxxxxxxxxxxxxxx")
.build();
when(compteRepository.getByLogin(anyString())).thenReturn(Optional.of(acc));
Optional<Compte> optional = CompteService.getByLogin("xxx#gmail.com");
Compte Compte = optional.get();
Assertions.assertSame(1L, acc.getId());
Assertions.assertSame("xxx#gmail.com", Compte.getLogin());
}
#DisplayName("Should update existing user")
#Test
public void given_edited_Compte_then_update_user() {
Compte acc = Compte.builder().id(1L)
.firstName("Luc")
.lastName("JOJO")
.email("xxx#gmail.com")
.password("xxxxxxxxxxxxxxx")
.build();
when(compteRepository.getById(anyLong())).thenReturn(Optional.of(acc));
CompteUpdatedData updatedData = CompteUpdatedData.builder()
.firstName("Moos")
.lastName("Man")
.build();
CompteService.update(1L, updatedData);
Assertions.assertSame("Moos", acc.getFirstName());
}
private List<Compte> getComptes() {
List<Compte> Comptes = new ArrayList<>();
Compte acc1 = Compte.builder()
.id(1L)
.firstName("Luc")
.lastName("JOJO")
.email("xxx#gmail.com")
.login("xxx#gmail.com")
.build();
Comptes.add(acc1);
Compte acc2= Compte.builder()
.id(2L)
.firstName("Jean")
.lastName("KELLY")
.email("jean.kelly#gmail.com")
.login("jean.kelly#gmail.com")
.build();
Comptes.add(acc2);
Compte acc3= Compte.builder()
.id(3L)
.firstName("Marc")
.lastName("BARBY")
.email("marc.barby#gmail.com")
.login("marc.barby#gmail.com")
.build();
Comptes.add(acc3);
return Comptes;
}
}
Spring boot application
package com.compte;
import com.compte.application.CompteService;
import com.compte.application.impl.CompteServiceImpl;
import com.compte.domain.repository.CompteRepository;
import com.compte.infrastructure.repository.database.CompteDBRepositiry;
import com.ombsc.bargo.common.config.SwaggerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.client.LinkDiscoverer;
import org.springframework.hateoas.client.LinkDiscoverers;
import org.springframework.hateoas.mediatype.collectionjson.CollectionJsonLinkDiscoverer;
import org.springframework.plugin.core.SimplePluginRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.ArrayList;
import java.util.List;
#ComponentScan({"com.compte.interfaces.interfaces"})
#SpringBootApplication
#Import({SwaggerConfig.class})
public class CompteApplication {
public static void main(String[] args) {
SpringApplication.run(CompteApplication.class, args);
}
#Bean
public CompteRepository getRepository() {
return new CompteDBRepositiry();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public CompteService CompteService(CompteRepository repository, PasswordEncoder passwordEncoder) {
return new CompteServiceImpl(repository, passwordEncoder);
}
#Bean
public LinkDiscoverers discovers() {
List<LinkDiscoverer> plugins = new ArrayList<>();
plugins.add(new CollectionJsonLinkDiscoverer());
return new LinkDiscoverers(SimplePluginRegistry.create(plugins));
}
}
The mocks need to be initialized before they can be used. There are several options to do this.
The first option would be to use #SpringExtension which will initialize the mocks annotated with #MockBean:
#ExtendWith(SpringExtension.class)
public class CompteServiceImplTest {
#Autowired
private CompteService CompteService;
#MockBean
private compteRepository compteRepository;
// ...
}
This will make sure that the repository bean is mocked before the service bean is autowired.
However, since you are writing a unit test for the service, you don't need the Spring extension at all. The second option is to use #Mock instead of #MockBean, and call #InjectMocks in conjunction with the MockitoExtension for constructing the service under test:
#ExtendWith(MockitoExtension.class)
public class CompteServiceImplTest {
#InjectMocks
private CompteService CompteService;
#Mock
private compteRepository compteRepository;
// ...
}
Alternatively, you could just call MockitoAnnotations.initMocks(), which will initialize the mocks annotated with #Mock, and use constructor injection for your service:
public class CompteServiceImplTest {
private CompteService CompteService;
#Mock
private compteRepository compteRepository;
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
CompteService = new CompteServiceImpl(compteRepository, ...);
}
// ...
}
Finally, you could do it all without annotations just by calling Mockito.mock() directly:
public class CompteServiceImplTest {
private compteRepository compteRepository;
#BeforeEach
void setUp() {
compteRepository = Mockito.mock();
CompteService = new CompteServiceImpl(compteRepository, ...);
}
// ...
}
Here , I want to make a SpringBoot and MyBatis application use dynamic datasource by AOP; But the AOP is always execute after query from database, so switch datasource is invalid because select is finished.
All my code is in https://github.com/helloworlde/SpringBoot-DynamicDataSource/tree/aspect_dao
My dependence is
compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-aop')
runtime('mysql:mysql-connector-java')
And application.properties
application.server.db.master.driver-class-name=com.mysql.jdbc.Driver
application.server.db.master.url=jdbc:mysql://localhost/redisapi?useSSL=false
application.server.db.master.port=3306
application.server.db.master.username=root
application.server.db.master.password=ihaveapen*^##
#application.server.db.master.database=123456
#
## application common config
application.server.db.slave.driver-class-name=com.mysql.jdbc.Driver
application.server.db.slave.url=jdbc:mysql:/localhost/redisapi2?useSSL=false
application.server.db.slave.port=3306
application.server.db.slave.username=root
application.server.db.slave.password=123456
#application.server.db.slave.database=redisapi
mybatis.type-aliases-package=cn.com.hellowood.dynamicdatasource.mapper
mybatis.mapper-locations=mappers/**Mapper.xml
Table
CREATE TABLE product(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
price DOUBLE(10,2) NOT NULL DEFAULT 0
);
DataSourceConfigur.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
public class DataSourceConfigurer {
#Bean("master")
#Primary
#ConfigurationProperties(prefix = "application.server.db.master")
public DataSource master() {
return DataSourceBuilder.create().build();
}
#Bean("slave")
#ConfigurationProperties(prefix = "application.server.db.slave")
public DataSource slave() {
return DataSourceBuilder.create().build();
}
#Bean("dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("master", master());
dataSourceMap.put("slave", slave());
// Set master datasource as default
dynamicRoutingDataSource.setDefaultTargetDataSource(master());
// Set master and slave datasource as target datasource
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
// To put datasource keys into DataSourceContextHolder to judge if the datasource is exist
DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
return dynamicRoutingDataSource;
}
#Bean
#ConfigurationProperties(prefix = "mybatis")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// Here is very important, if don't config this, will can't switch datasource
// put all datasource into SqlSessionFactoryBean, then will autoconfig SqlSessionFactory
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
return sqlSessionFactoryBean;
}
#Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
DynamicRoutingDataSource.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
protected Object determineCurrentLookupKey() {
logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
DynamicDataSourceContextHolder.java
package cn.com.hellowood.dynamicdatasource.configuration;
import java.util.ArrayList;
import java.util.List;
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
#Override
protected String initialValue() {
return "master";
}
};
public static List<Object> dataSourceKeys = new ArrayList<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
}
DynamicDataSourceAspect.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
#Aspect
#Order(-100) // To ensure execute before #Transactional
#Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
private final String QUERY_PREFIX = "select";
#Pointcut("execution( * cn.com.hellowood.dynamicdatasource.mapper.*.*(..))")
public void daoAspect() {
}
#Before("daoAspect()")
public void switchDataSource(JoinPoint point) {
if (point.getSignature().getName().startsWith(QUERY_PREFIX)) {
DynamicDataSourceContextHolder.setDataSourceKey("slave");
logger.info("Switch DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
}
}
#After("daoAspect())")
public void restoreDataSource(JoinPoint point) {
DynamicDataSourceContextHolder.clearDataSourceKey();
logger.info("Restore DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
}
}
And have Controller, Service and Dao for query, But although I set Order of aspect as -100, it still execute query before AOP, could anyone find where is wrong, Thank you very much.
This is log screenshot
Finally I fixed this issue, Because I injected Bean of DataSourceTransactionManager, So transaction will be open in Service, so the aspect of DAO is not work until transaction finished.
Delete this code:
#Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
I'm trying to put together an SDK that uses Spring internally through a context it manages of its own. I want the jar that gets built to be usable regardless of whether or not Spring is in use on the application that wants to use the SDK.
I have something that works when it is running on its own. However if I attempt to use the SDK inside another Spring context (in my case a Spring Boot based application) I get a org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type exception.
Try as I might I cannot understand how to get this working, or indeed what I am doing wrong. The classes below show what I'm doing, the org.example.testapp.MySDKTest fails with the exception while the org.example.test.MySDKTest successfully passes. Sorry there is so much code but I can't reproduce the issue with a simplified case.
SDK source
package org.example.mysdk;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.example.mysdk.MyService;
import org.example.mysdk.MyServiceConfiguration;
public final class MySDK {
private static ApplicationContext applicationContext;
public static <T extends MyService> T getService(Class<? extends MyService> clazz, MyServiceConfiguration configuration) {
T tmp = (T) getApplicationContext().getBean(clazz);
tmp.setConfiguration(configuration);
return tmp;
}
private static ApplicationContext getApplicationContext() {
if (applicationContext == null) {
applicationContext = new AnnotationConfigApplicationContext(SpringContext.class);
}
return applicationContext;
}
}
.
package org.example.mysdk;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class MyService {
private MyServiceConfiguration configuration;
#Autowired
private MyAutowiredService myAutowiredService;
MyService() {
}
MyService(MyServiceConfiguration configuration) {
super();
this.configuration = configuration;
}
public MyServiceConfiguration getConfiguration() {
return configuration;
}
void setConfiguration(MyServiceConfiguration configuration) {
this.configuration = configuration;
}
String getSomething(String in) {
return "something + " + myAutowiredService.getThing(configuration.getValue()) + " and " + in;
}
}
.
package org.example.mysdk;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class MyServiceImpl1 extends MyService {
public MyServiceImpl1() {
}
public MyServiceImpl1(MyServiceConfiguration configuration) {
super(configuration);
}
public String method1() {
return this.getSomething("method1");
}
}
.
package org.example.mysdk;
public class MyServiceConfiguration {
private String value;
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
.
package org.example.mysdk;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
#Service
public class MyAutowiredService {
private String thing = "a value";
public String getThing(String in) {
return thing + " " + in;
}
#PostConstruct
void init() {
System.out.println("MyAutowiredService bean created");
}
}
.
package org.example.mysdk;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = {
"org.example.mysdk"
})
public class SpringContext {
}
Tests
This first test fails with a org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type exception,
package org.example.testapp;
import static org.junit.Assert.*;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
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.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = App.class, loader = AnnotationConfigContextLoader.class)
public class MySDKTest {
#Autowired
MyServiceImpl1 service;
#Test
public void test() {
MyServiceConfiguration conf = service.getConfiguration();
assertEquals(conf.getValue(), "this is the instance configuration");
}
}
.
package org.example.testapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
#Configuration
#ComponentScan(basePackages = {
"org.example.testapp"
})
public class App {
#Bean
public MyServiceImpl1 myServiceImpl1() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
return MySDK.getService(MyServiceImpl1.class, configuration);
}
}
and this test succeeds,
package org.example.test;
import static org.junit.Assert.*;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
import org.junit.Test;
public class MySDKTest {
#Test
public void test() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
MyServiceImpl1 service = MySDK.getService(MyServiceImpl1.class, configuration);
assertEquals(service.getConfiguration().getValue(), "this is the instance configuration");
}
}
If I've gone about this the completely wrong way I'm happy to hear suggestions of how this should be done differently!
You have to modify two files.
First App.java, it should scan for "org.example.mysdk" package to inject myAutowiredService in abstract class MyService, If not it has to be created in App.java. And the name of the MyServiceImpl1 bean must be different from myServiceImpl1 as it will conflict.
package org.example.testapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
#Configuration
#ComponentScan(basePackages = {
"org.example.testapp", "org.example.mysdk"
})
public class App {
#Bean
public MyServiceImpl1 myServiceImpl() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
return MySDK.getService(MyServiceImpl1.class, configuration);
}
}
Then secondly in MySDKTest.java should inject myServiceImpl which was created in App.java
import static org.junit.Assert.*;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
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.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = App.class, loader = AnnotationConfigContextLoader.class)
public class MySDKTest {
#Autowired
MyServiceImpl1 myServiceImpl;
#Test
public void createOxiAccountService() {
MyServiceConfiguration conf = myServiceImpl.getConfiguration();
assertEquals(conf.getValue(), "this is the instance configuration");
}
}
Just started using MongoDB with JAVA.
Set up the following JavaObject
package com.foo.bar
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "cameras")
public class Camera {
#Id
private int id= -1;
String name = "";
String orientation = "";
boolean tempdis = false;
int refreshRate = -1;
String cityCode = "";
String provider = "";
Location location;
//Gets & Sets Below
}
I then have the following config
package com.foo.bar.config
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import com.mongodb.MongoClient;
#Configuration
public class SpringMongoConfig {
public #Bean
MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(new MongoClient(), "myDB");
}
public #Bean
MongoTemplate mongoTemplate() throws Exception {
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory());
return mongoTemplate;
}
}
And in my main application
package com.foo.bar
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.foo.bar.config.SpringMongoConfig;
import com.foo.bar.Camera;
#Configuration
#EnableMongoRepositories
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(
SpringMongoConfig.class);
MongoOperations mongoOperation = (MongoOperations) ctx
.getBean("mongoTemplate");
List<Camera> listUser = mongoOperation.findAll(Camera.class);
System.out.println(listUser.size());
}
}
Running this code is getting me a size of 0. However, in the command line I get the following
C:\mongodb\bin>mongo.exe
MongoDB shell version: 2.4.8
connecting to: test
> use myDB
switched to db myDB
> db.Camera.count()
1018
>
Is there something I'm missing?
#Document(collection = "cameras")
public class Camera {
Camera is mapped to collection cameras in your code.
and you are looking up in collection Camera using db.Camera.count()