How can I mock HikariDataSource and DataSourceProperties using springboot and Junit5 and Mockito?
I found one possible solution but it seems to be rather very lengthy approach to test a single method, you can check here
I am trying to unit test one of the method 'ping' in my class but getting exception:
java.lang.NullPointerException
at com.my.app.configuration.DbConfigurationTest.shouldReturnPing(DbConfgiurationTest.java:37)
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.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
Test Class:
import com.my.app.config.DbConfiguration;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.test.context.TestConfiguration;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
#TestConfiguration
class DbConfigurationTest
{
#InjectMocks static DbConfiguration dbConfiguration;
DataSourceProperties dataSourceProperties= Mockito.mock(DataSourceProperties.class);
#Test
public void shouldReturnPing()
{
// expect
boolean expectedResult = true;
when(dbConfiguration.dataSourceProperties()).thenReturn(dataSourceProperties);
when(dbConfiguration.dataSource(dataSourceProperties).isRunning()).thenReturn(true);
boolean actualResult = dbConfiguration.ping();
assertThat(actualResult, is(equalTo(expectedResult)));
}
}
Method class which needs to be tested:
import com.zaxxer.hikari.HikariDataSource;
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;
/**
* Database configuration. Only properties override is required, everything else comes from spring boot auto-config.
*/
#Configuration
public class DbConfiguration
{
/**
* Overrides the auto-configured bean in order to specify a different prefix for the DataSourceProperties
*
* #return A DataSourceProperties bean
*/
#Bean
#Primary
#ConfigurationProperties("my.datasource")
public DataSourceProperties dataSourceProperties()
{
return new DataSourceProperties();
}
/**
* Overrides the {#link javax.sql.DataSource} bean creation from
* {#link org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration}
* to specify a different prefix for the {#link org.springframework.boot.context.properties.ConfigurationProperties}
*
* #param dataSourceProperties The properties to configure the datasource
* #return A {#link com.zaxxer.hikari.HikariDataSource} instance
*/
#Bean
#ConfigurationProperties("my.datasource.configuration")
public HikariDataSource dataSource(final DataSourceProperties dataSourceProperties)
{
return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
/**
* Ping the service.
*
* #return the boolean
*/
public boolean ping()
{
return dataSource(dataSourceProperties()).isRunning();
}
}
How can I successfully test the 'ping' method? I tried to create mock but failed.
Thanks in advance :)
I would suggest moving the ping() method to a separate class and autowire the DataSource there. For example:
#Component
public class DatabaseHealthCheck {
public DatabaseHealthCheck(HikariDataSource dataSource) {
this.dataSource = dataSource;
}
public boolean ping() {
return dataSource.isRunning();
}
}
And now you can test it like this:
// Mock only the DataSource, either with #Mock or like this
HikariDataSource dataSource = mock(HikariDataSource.class);
DatabaseHealthCheck healthCheck = new DatabaseHealthCheck(dataSource);
when(dataSource.isRunning()).thenReturn(true);
assertThat(healthCheck.ping()).isTrue();
Related
I'm trying to figure out if it's possible to catch transaction events like it's described here, but with the reactive client?
If it's not possible I would appreciate if someone provide an example how it can be implement manually. I want to be able to add some business logic in my application before transaction start, before transaction commit and after transaction commit. And I think that events are best suited for such logic. Thanks in advance.
Finally, I've found a way how it can be implemented manually. Reactive hibernate has such a method in the Mutiny session implementation:
Uni<T> executeInTransaction(Function<Mutiny.Transaction, Uni<T>> work) {
return work.apply( this )
// only flush() if the work completed with no exception
.call( this::flush )
.call( this::beforeCompletion )
// in the case of an exception or cancellation
// we need to rollback the transaction
.onFailure().call( this::rollback )
.onCancellation().call( this::rollback )
// finally, when there was no exception,
// commit or rollback the transaction
.call( () -> rollback ? rollback() : commit() )
.call( this::afterCompletion );
}
So, as you can see, two methods (beforeCompletion and afterCompletion) are called in the chain, which allows us to add custom logic before and after transaction commit. Those methods execute contract implementations from the queue. I'll show you an example of the "before" event.
First of all, we should create some qualifier annotations.
The #Entity annotation we are going to use to attach event listeners for the specific entity event:
package com.example.annotation;
import java.io.Serial;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Qualifier;
import lombok.EqualsAndHashCode;
import com.example.model.BaseEntity;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Qualifier
#Retention(RUNTIME)
#Target({METHOD, PARAMETER, FIELD, TYPE})
#Documented
public #interface Entity {
Class<? extends BaseEntity> value();
#EqualsAndHashCode(callSuper = true)
final class Literal extends AnnotationLiteral<Entity> implements Entity {
#Serial
private static final long serialVersionUID = 2137611959567040656L;
private final Class<? extends BaseEntity> value;
private Literal(Class<? extends BaseEntity> value) {
this.value = value;
}
public static Literal of(Class<? extends BaseEntity> value) {
return new Literal(value);
}
#Override
public Class<? extends BaseEntity> value() {
return value;
}
}
}
Let's imagine we have Book and Author entities in our service and both of them extend a BaseEntity model/interface. This base entity is used here as some kind of qualifier, which will be used later.
And some annotations for CRUD actions, here as an example of the "create" action (the same is for the "update" action):
#Qualifier
#Retention(RUNTIME)
#Target({METHOD, PARAMETER, FIELD, TYPE})
#Documented
public #interface Create {
#EqualsAndHashCode(callSuper = true)
final class Literal extends AnnotationLiteral<Create> implements Create {
private static final long serialVersionUID = 2137611959567040656L;
public static final Literal INSTANCE = new Literal();
private Literal() {
}
}
}
Next we create the class, which will trigger events. In this example we register "pre-insert" and "pre-update" listeners:
package com.example.observer;
import java.io.Serial;
import java.lang.annotation.Annotation;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.CDI;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.*;
import org.hibernate.reactive.session.ReactiveSession;
import com.example.annotation.Entity;
import com.example.annotation.operation.Create;
import com.example.annotation.operation.Update;
import com.example.model.BaseEntity;
import com.example.observer.action.EventReactiveBeforeTransactionCompletionProcess;
/**
* {#inheritDoc}
* <p>Component, which registers 'pre' events</p>
*/
#Slf4j
#ApplicationScoped
public class TransactionProcessRegistrarEventListener implements PreInsertEventListener, PreUpdateEventListener {
#Serial
private static final long serialVersionUID = 6763048376606381859L;
/**
* {#inheritDoc}
*
* #param event event
*/
#Override
public boolean onPreInsert(PreInsertEvent event) {
return register(event, Create.Literal.INSTANCE);
}
/**
* {#inheritDoc}
*
* #param event event
*/
#Override
public boolean onPreUpdate(PreUpdateEvent event) {
return register(event, Update.Literal.INSTANCE);
}
/**
* Register processes
*
* #param event event
* #return result
*/
#SuppressWarnings("unchecked")
private boolean register(AbstractPreDatabaseOperationEvent event, Annotation qualifier) {
Class<? extends BaseEntity> clazz = (Class<? extends BaseEntity>) event.getEntity().getClass();
log.debug("registering '{}' instances. Entity: {}", event.getClass().getSimpleName(), clazz.getSimpleName());
final SessionImplementor session = event.getSession();
List<EventReactiveBeforeTransactionCompletionProcess> beforeProcesses = CDI.current()
.select(EventReactiveBeforeTransactionCompletionProcess.class, Entity.Literal.of(clazz), qualifier)
.stream().toList();
if (beforeProcesses.isEmpty())
log.debug("no 'before' processes found");
beforeProcesses.forEach(process -> {
process.setEvent(event);
((ReactiveSession) session).getReactiveActionQueue()
.registerProcess(process);
log.debug("process {} has been successfully registered", process.getClass().getSimpleName());
});
return false;
}
}
As you can see here we have a custom EventReactiveBeforeTransactionCompletionProcess interface, it will allow us to set current event to the hibernate process (and retrieve it later in the event). Let's create it:
package com.example.observer.action;
import org.hibernate.event.spi.AbstractPreDatabaseOperationEvent;
import org.hibernate.reactive.engine.ReactiveBeforeTransactionCompletionProcess;
public interface EventReactiveBeforeTransactionCompletionProcess extends ReactiveBeforeTransactionCompletionProcess {
<T extends AbstractPreDatabaseOperationEvent> void setEvent(T event);
<T extends AbstractPreDatabaseOperationEvent> T getEvent();
}
Now we have everything to create a custom hibernate integrator, which will allow us to register listeners.
package com.example.observer;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import javax.enterprise.context.ApplicationScoped;
#Slf4j
#ApplicationScoped
public class EventListenerIntegrator implements Integrator {
/**
* {#inheritDoc}
*
* #param metadata The "compiled" representation of the mapping information
* #param sessionFactory The session factory being created
* #param serviceRegistry The session factory's service registry
*/
#Override
public void integrate(Metadata metadata,
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
log.debug("registering {} integrator...", getClass().getSimpleName());
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, new TransactionProcessRegistrarEventListener());
eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, new TransactionProcessRegistrarEventListener());
}
/**
* {#inheritDoc}
*
* #param sessionFactory The session factory being closed.
* #param serviceRegistry That session factory's service registry
*/
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
// intentionally do nothing
}
}
And finally, the listener itself (you can retrieve the event here and the entity from it using parent getEvent method):
package com.example.observer.action.before.create.book.validator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import io.quarkus.arc.Priority;
import io.quarkus.arc.Unremovable;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.event.spi.AbstractPreDatabaseOperationEvent;
import org.hibernate.reactive.session.ReactiveSession;
import com.example.annotation.Entity;
import com.example.annotation.operation.Create;
import com.example.model.Book;
import com.example.observer.action.EventReactiveBeforeTransactionCompletionProcess;
#Getter
#Setter
#Slf4j
#Priority(10)
#Entity(Book.class)
#Create
#Unremovable
#Dependent
public class BookValidator implements EventReactiveBeforeTransactionCompletionProcess {
private AbstractPreDatabaseOperationEvent event;
/**
* {#inheritDoc}
*
* #param session The session on which the transaction is preparing to complete.
*/
#Override
public CompletionStage<Void> doBeforeTransactionCompletion(ReactiveSession session) {
log.debug("validating, enriching or everything you want with the book entity...");
return CompletableFuture.completedStage(null);
}
}
That's all. If anyone knows a better realization, please let me know.
I am upgrading old Java EE application to Spring based solution. In the old applications there were custom annotations to create proxy inject proxy bean and intercept the method invocation [Interceptor classes implements MethodInterceptor) or (implements InvocationHandler), which used to perform some before and after execution stuff.
We have replaced those custom annotations with Spring marker interfaces like #Service, #Repository etc. and we are able to use #Autowire the bean instances. Now my question is how to intercept these autowired beans to perform per and post execution activities. One solution I can think is to use Spring AOP and use #Around pointcut. Just want to know is there any other and better alternative which can be used like
extending org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
Using BeanFactoryPostProcessor or BeanPostProcessor.
Using InstantiationAwareBeanPostProcessor
I have used this alternative instead of AOP. I have used Spring's bean pre & post processor call back. Below is the code snippet.
Application Context Provider, to get Spring beans statically
package com.appname.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* #author dpoddar
*
*/
#Component("applicationContextProvider")
public class ApplicationContextProvider implements ApplicationContextAware{
private static ApplicationContext ctx = null;
public static ApplicationContext getApplicationContext() {
return ctx;
}
#Override
#Autowired
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
ApplicationContextProvider.ctx = ctx;
}
/**
* Returns the Spring managed bean instance of the given class type if it exists.
* Returns null otherwise.
* #param beanClass
* #return
*/
public static <T extends Object> T getBean(Class<T> beanClass) {
return ctx.getBean(beanClass);
}
/**
* Returns the Spring managed bean instance of the given class type if it exists.
*
* #param <T>
* #param name
* #param beanClass
* #return
*/
public static <T extends Object> T getBean(String name,Class<T> beanClass) {
return ctx.getBean(name,beanClass);
}
}
Spring Bean Post Processor, InstantiationAwareBeanPostProcessor adds before and after initialization call backs
package com.appname.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.SpringProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import com.appname.core.ExecutionContext;
import com.appname.core.di.FacadeService;
import com.appname.interceptors.BusinesServiceInterceptor;
import com.appname.interceptors.FacadeServiceInterceptor;
import com.appname.interceptors.RepositoryInterceptor;
import net.sf.cglib.proxy.Enhancer;
/**
* #author dpoddar
*
*/
#Component
public class AppSpringBeanPostProcessor extends AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
private static Logger logger = LoggerFactory.getLogger(AppSpringBeanPostProcessor.class);
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
Class<?> clazz = bean.getClass();
AutowireCapableBeanFactory factory = ApplicationContextProvider.getApplicationContext().getAutowireCapableBeanFactory();
if(clazz.isAnnotationPresent(FacadeService.class)) {
//This is to instatiate InvocationHandler classes
//return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new FacadeServiceInterceptor(bean));
FacadeServiceInterceptor interceptor = new FacadeServiceInterceptor();
Enhancer e = new Enhancer();
e.setSuperclass(clazz);
e.setInterfaces(new Class[]{SpringProxy.class}); /// Identification Spring-generated proxies
e.setCallback(interceptor);
Object o = e.create();
factory.autowireBean( o ); //Autowire Bean dependecies to the newly created object
return o;
}else if(clazz.isAnnotationPresent(Service.class)) {
BusinesServiceInterceptor interceptor = new BusinesServiceInterceptor();
Enhancer e = new Enhancer();
e.setSuperclass(clazz);
e.setInterfaces(new Class[]{SpringProxy.class});
e.setCallback(interceptor);
Object o = e.create();
factory.autowireBean( o );
return o;
}else if(clazz.isAnnotationPresent(Repository.class)) {
ExecutionContext.newInstance();
RepositoryInterceptor interceptor = new RepositoryInterceptor();
Enhancer e = new Enhancer();
e.setSuperclass(clazz);
e.setInterfaces(new Class[]{SpringProxy.class});
e.setCallback(interceptor);
return e.create();
}else {
return bean;
}
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
I am implementing OAuth2 using spring boot. And followed this example for the same
Spring-oauth2-jpa-example
Video Tutorial
After implementing I have successfully able to generate access token. But the problem is when I try to access protected resource in my case /api. I am getting 404 not found.
Main Method
package com.gatimaanBoot;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.gatimaanBoot.security.UserRepository;
#SpringBootApplication
public class GatimaanBootApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
System.out.println("booting....");
SpringApplication.run(GatimaanBootApplication.class, args);
}
/* #Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
//System.out.println(beanName);
}
};
}*/
/**
* Password grants are switched on by injecting an AuthenticationManager.
* Here, we setup the builder so that the userDetailsService is the one we coded.
* #param builder
* #param repository
* #throws Exception
*/
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository, com.gatimaanBoot.security.service.UserService service) throws Exception {
//Setup a default user if db is empty
if (repository.count()==0)
service.save(new com.gatimaanBoot.security.entities.User("user", "user", Arrays.asList(new com.gatimaanBoot.security.entities.Role("USER"), new com.gatimaanBoot.security.entities.Role("ACTUATOR"))));
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
}
/**
* We return an instance of our CustomUserDetails.
* #param repository
* #return
*/
private UserDetailsService userDetailsService(final UserRepository repository) {
return username -> new CustomUserDetails(repository.findByUsername(username));
}
}
AuthorizationServer
/**
* Copyright 2017 Duronto Technology (P) Limited . All Rights Reserved.
* Duronto Technology PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.gatimaanBoot.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
/**
*
* #version 1.0, 09-Jul-2017
* #author Nikhil
*/
/**
* Configures the authorization server.
* The #EnableAuthorizationServer annotation is used to configure the OAuth 2.0 Authorization Server mechanism,
* together with any #Beans that implement AuthorizationServerConfigurer (there is a handy adapter implementation with empty methods).
*/
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private PasswordEncoder passwordEncoder;
/**
* Setting up the endpointsconfigurer authentication manager.
* The AuthorizationServerEndpointsConfigurer defines the authorization and token endpoints and the token services.
* #param endpoints
* #throws Exception
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
/**
* Setting up the clients with a clientId, a clientSecret, a scope, the grant types and the authorities.
* #param clients
* #throws Exception
*/
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("password","authorization_code","implicit")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT").scopes("read","write","trust")
.resourceIds("oauth2-resource").accessTokenValiditySeconds(5000).secret("secret");
}
/**
* We here defines the security constraints on the token endpoint.
* We set it up to isAuthenticated, which returns true if the user is not anonymous
* #param security the AuthorizationServerSecurityConfigurer.
* #throws Exception
*/
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
}
ResourceServer
package com.gatimaanBoot.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable().and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/api/**").authenticated();
}
}
Application.properties
#Application Path
server.contextPath = /gatimaanBoot
security.oauth2.resource.filter-order = 3
# Database
spring.datasource.driver = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/train?zeroDateTimeBehavior=convertToNull
spring.datasource.username = root
spring.datasource.password = root
# Hibernate
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.show_sql= true
spring.jpa.properties.hibernate.hbm2ddl.auto= update
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
Controller
/**
* Copyright 2017 Duronto Technology (P) Limited . All Rights Reserved.
* Duronto Technology PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.gatimaanBoot.station.controller;
/**
*
* #version 1.0, 24-Feb-2017
* #author Deepak Bisht
* #author Nikhil Mishra
*
*
*/
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.gatimaanBoot.station.dto.StationResponseDTO;
import com.gatimaanBoot.station.model.Station;
import com.gatimaanBoot.station.services.StationService;
#Controller
#RequestMapping("/api/")
public class StationController {
private static final Logger LOGGER = LoggerFactory.getLogger(StationController.class);
#Autowired
StationService stationService;
// get data for particular station
#RequestMapping(value = "/v1.0/station/{stationCode}/", method = RequestMethod.GET)
public #ResponseBody Station getStation(#PathVariable String stationCode) {
Station station = null;
try {
station = stationService.getStation(stationCode);
} catch (Exception e) {
e.printStackTrace();
}
return station;
}
/* Getting List of stations in Json format in pagination */
#RequestMapping(value = "/v1.0/station/", method = RequestMethod.GET)
public #ResponseBody List<Station> getStationList(
#RequestParam(value = "page", required = false, defaultValue = "0") int page) {
List<Station> stationList = null;
try {
stationList = stationService.getStationList(page);
} catch (Exception e) {
LOGGER.debug("Station Controller : " + e.getMessage());
e.printStackTrace();
}
return stationList;
}
// insert new station
#RequestMapping(value = "/v1.0/station/", method = RequestMethod.POST)
public #ResponseBody StationResponseDTO insertStation(#RequestBody Station station) {
StationResponseDTO stationDTO = null;
try {
stationDTO = stationService.insertStation(station);
} catch (Exception e) {
e.printStackTrace();
}
return stationDTO;
}
// insert new station
#RequestMapping(value = "/v1.0/station/", method = RequestMethod.PUT)
public #ResponseBody StationResponseDTO updateStation(#RequestBody Station station) {
StationResponseDTO stationDTO = null;
try {
stationDTO = stationService.updateStation(station);
} catch (Exception e) {
e.printStackTrace();
}
return stationDTO;
}
// delete a station
#RequestMapping(value = "/v1.0/station/", method = RequestMethod.DELETE)
public #ResponseBody StationResponseDTO deleteStation(#RequestBody Station station) {
StationResponseDTO stationDTO = null;
try {
stationDTO = stationService.deleteStation(station);
} catch (Exception e) {
e.printStackTrace();
}
return stationDTO;
}
#RequestMapping(value = "/v1.0/station/list/", method = RequestMethod.POST)
public #ResponseBody List<Station> getTrainList(#RequestBody ArrayList<String> stationList) {
return stationService.getStationListBulk(stationList);
}
}
Thanks.
I am working on a mysql master slave replication. I am using spring data jpa(spring boot).
What I needed is all write operations to go to master server and read-only operations to be equally distributed among multiple read-only slaves.
For that I need to:
Use special JDBC driver: com.mysql.jdbc.ReplicationDriver
Set replication: in the URL:
spring:
datasource:
driverClassName: com.mysql.jdbc.ReplicationDriver
url: jdbc:mysql:replication://127.0.0.1:3306,127.0.0.1:3307/MyForum?user=root&password=password&autoReconnect=true
test-on-borrow: true
validation-query: SELECT 1
database: MYSQL
Auto commit needs to be turned off. (*)
Connection needs to be set to read-only.
To ensure JDBC Connection is set to read-only, I created an annotation and a simple AOP interceptor.
Annotation
package com.xyz.forum.replication;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by Bhupati Patel on 02/11/15.
*/
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ReadOnlyConnection {
}
Interceptor
package com.xyz.forum.replication;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
/**
* Created by Bhupati Patel on 02/11/15.
*/
#Aspect
#Component
public class ConnectionInterceptor {
private Logger logger;
public ConnectionInterceptor() {
logger = LoggerFactory.getLogger(getClass());
logger.info("ConnectionInterceptor Started");
}
#Autowired
private EntityManager entityManager;
#Pointcut("#annotation(com.xyz.forum.replication.ReadOnlyConnection)")
public void inReadOnlyConnection(){}
#Around("inReadOnlyConnection()")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
try{
session.doWork(readOnlyWork);
return pjp.proceed();
} finally {
readOnlyWork.switchBack();
}
}
}
Following is my spring data repository
package com.xyz.forum.repositories;
import com.xyz.forum.entity.Topic;
import org.springframework.data.repository.Repository;
import java.util.List;
/**
* Created by Bhupati Patel on 16/04/15.
*/
public interface TopicRepository extends Repository<Topic,Integer>{
Topic save(Topic topic);
Topic findByTopicIdAndIsDeletedFalse(Integer topicId);
List<Topic> findByIsDeletedOrderByTopicOrderAsc(Boolean isDelete);
}
Following is my Manager(Service) class.
package com.xyz.forum.manager;
import com.xyz.forum.domain.entry.impl.TopicEntry;
import com.xyz.forum.domain.exception.impl.AuthException;
import com.xyz.forum.domain.exception.impl.NotFoundException;
import com.xyz.forum.entity.Topic;
import com.xyz.forum.replication.ReadOnlyConnection;
import com.xyz.forum.repositories.TopicRepository;
import com.xyz.forum.utils.converter.TopicConverter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
* Created by Bhupati Patel on 16/04/15.
*/
#Repository
public class TopicManager {
#Autowired
TopicRepository topicRepository;
#Transactional
public TopicEntry save(TopicEntry topicEntry) {
Topic topic = TopicConverter.fromEntryToEntity(topicEntry);
return TopicConverter.fromEntityToEntry(topicRepository.save(topic));
}
#ReadOnlyConnection
public TopicEntry get(Integer id) {
Topic topicFromDb = topicRepository.findByTopicIdAndIsDeletedFalse(id);
if(topicFromDb == null) {
throw new NotFoundException("Invalid Id", "Topic Id [" + id + "] doesn't exist ");
}
return TopicConverter.fromEntityToEntry(topicFromDb);
}
}
In the above code #ReadOnlyConnection annotation is specified in manager or service layer. Above pieces of code works fine for me. It is a trivial case where in the service layer I am only reading from slave db and writing into master db.
Having said that my actual requirement is I should be able to use #ReadOnlyConnection in repository level itself because I have quite a few business logic where I do both read/write operation in other classes of service layer.Therefore I can't put #ReadOnlyConnection in service layer.
I should be able to use something like this
public interface TopicRepository extends Repository<Topic,Integer>{
Topic save(Topic topic);
#ReadOnlyConnection
Topic findByTopicIdAndIsDeletedFalse(Integer topicId);
#ReadOnlyConnection
List<Topic> findByIsDeletedOrderByTopicOrderAsc(Boolean isDelete);
}
Like spring's #Transactional or #Modifying or #Query annotation. Following is an example of what I am referring.
public interface AnswerRepository extends Repository<Answer,Integer> {
#Transactional
Answer save(Answer answer);
#Transactional
#Modifying
#Query("update Answer ans set ans.isDeleted = 1, ans.deletedBy = :deletedBy, ans.deletedOn = :deletedOn " +
"where ans.questionId = :questionId and ans.isDeleted = 0")
void softDeleteBulkAnswers(#Param("deletedBy") String deletedBy, #Param("deletedOn") Date deletedOn,
#Param("questionId") Integer questionId);
}
I am novice to aspectj and aop world, I tried quite a few pointcut regex in the ConnectionInterceptor but none of them worked. I have been trying this since a long time but no luck yet.
How to achieve the asked task.
I couldn't get a workaround of having my custom annotation #ReadOnlyConnection(like #Transactional) at a method level,but a small heck did work for me.
I am pasting the code snippet below.
#Aspect
#Component
#EnableAspectJAutoProxy
public class ConnectionInterceptor {
private Logger logger;
private static final String JPA_PREFIX = "findBy";
private static final String CUSTOM_PREFIX = "read";
public ConnectionInterceptor() {
logger = LoggerFactory.getLogger(getClass());
logger.info("ConnectionInterceptor Started");
}
#Autowired
private EntityManager entityManager;
#Pointcut("this(org.springframework.data.repository.Repository)")
public void inRepositoryLayer() {}
#Around("inRepositoryLayer()")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
if (StringUtils.startsWith(methodName, JPA_PREFIX) || StringUtils.startsWith(methodName, CUSTOM_PREFIX)) {
System.out.println("I'm there!" );
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
try{
session.doWork(readOnlyWork);
return pjp.proceed();
} finally {
readOnlyWork.switchBack();
}
}
return pjp.proceed();
}
}
So in the above code I am using a pointcut like following
#Pointcut("this(org.springframework.data.repository.Repository)")
public void inRepositoryLayer() {}
and what it does is
any join point (method execution only in Spring AOP) where the proxy implements the Repository interface
You can have a look it at
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html
Now all my repository read query methods either start with a prefix "findByXXX"(default spring-data-jpa readable method) or "readXXX"(custom read method with #Query annotation) which in my around method executions matched by the above pointcut. According to my requirement I am setting the JDBC Connection readOnly true.
Session session = entityManager.unwrap(Session.class);
ConnectionReadOnly readOnlyWork = new ConnectionReadOnly();
And my ConnectionReadOnly look like following
package com.xyz.forum.replication;
import org.hibernate.jdbc.Work;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Created by Bhupati Patel on 04/11/15.
*/
public class ConnectionReadOnly implements Work {
private Connection connection;
private boolean autoCommit;
private boolean readOnly;
#Override
public void execute(Connection connection) throws SQLException {
this.connection = connection;
this.autoCommit = connection.getAutoCommit();
this.readOnly = connection.isReadOnly();
connection.setAutoCommit(false);
connection.setReadOnly(true);
}
//method to restore the connection state before intercepted
public void switchBack() throws SQLException{
connection.setAutoCommit(autoCommit);
connection.setReadOnly(readOnly);
}
}
So above settings work for my requirement.
it seems that #Pointcut && #Around should be declared in some way like follows:
#Pointcut(value = "execution(public * *(..))")
public void anyPublicMethod() {
}
#Around("#annotation(readOnlyConnection)")
I'm using a jhipster project with cassandra as its repository. Now i also want to integrate elasticsearch with the same project. But getting errors which seem to be that the cassandra is trying to read #Document as its annotation.
Caused by: org.springframework.data.cassandra.mapping.VerifierMappingExceptions: com.shoptell.backoffice.repository.dto.IndexedMergeProductInfoDTO:
Cassandra entities must have the #Table, #Persistent or #PrimaryKeyClass Annotation
at org.springframework.data.cassandra.mapping.BasicCassandraPersistentEntityMetadataVerifier.verify(BasicCassandraPersistentEntityMetadataVerifier.java:45)
at org.springframework.data.cassandra.mapping.BasicCassandraPersistentEntity.verify(BasicCassandraPersistentEntity.java:198)
at org.springframework.data.mapping.context.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java:297)
at org.springframework.data.mapping.context.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java:256)
at org.springframework.data.mapping.context.AbstractMappingContext.initialize(AbstractMappingContext.java:372)
at org.springframework.data.cassandra.mapping.BasicCassandraMappingContext.initialize(BasicCassandraMappingContext.java:79)
at org.springframework.data.mapping.context.AbstractMappingContext.afterPropertiesSet(AbstractMappingContext.java:362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1633)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
... 144 more
IndexedMergeProductInfoDTO.java
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
#Document(indexName="mergeIndex",type="indexMergeProductInfo")
public class IndexedMergeProductInfoDTO {
#Id
private String id;
private String name;
private String metaCategory;
private String categoryName;
private String subCategoryName;
private String productBrand;
private String productSubBrand;
private String series;
private String model;
CassandraDataAutoConfiguration.java
import com.datastax.driver.core.Cluster;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.CassandraEntityClassScanner;
import org.springframework.data.cassandra.config.CassandraSessionFactoryBean;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.CassandraAdminOperations;
import org.springframework.data.cassandra.core.CassandraAdminTemplate;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import javax.inject.Inject;
/**
* {#link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} for Spring Data's Cassandra support.
* <p/>
* Registers a {#link org.springframework.data.cassandra.config.CassandraSessionFactoryBean} a {#link org.springframework.data.cassandra.core.CassandraAdminOperations} a {#link org.springframework.data.cassandra.mapping.CassandraMappingContext} and a
* {#link org.springframework.data.cassandra.convert.CassandraConverter} beans if no other beans of the same type are configured.
* <p/>
*/
#Configuration
#ConditionalOnClass({Cluster.class, CassandraAdminOperations.class})
#EnableConfigurationProperties(CassandraProperties.class)
#AutoConfigureAfter(CassandraAutoConfiguration.class)
public class CassandraDataAutoConfiguration {
#Inject
BeanFactory beanFactory;
#Inject
private CassandraProperties properties;
#Inject
private Cluster cluster;
#Bean
#ConditionalOnMissingBean
public CassandraSessionFactoryBean session() throws Exception {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(this.cluster);
session.setConverter(cassandraConverter());
session.setKeyspaceName(properties.getKeyspaceName());
return session;
}
#Bean
#ConditionalOnMissingBean
public CassandraAdminOperations cassandraTemplate() throws Exception {
return new CassandraAdminTemplate(session().getObject(), cassandraConverter());
}
#Bean
#ConditionalOnMissingBean
public CassandraMappingContext cassandraMapping() throws ClassNotFoundException {
BasicCassandraMappingContext bean = new BasicCassandraMappingContext();
bean.setInitialEntitySet(CassandraEntityClassScanner.scan(AutoConfigurationPackages.get(beanFactory)));
bean.setBeanClassLoader(beanFactory.getClass().getClassLoader());
return bean;
}
#Bean
#ConditionalOnMissingBean
public CassandraConverter cassandraConverter() throws Exception {
return new MappingCassandraConverter(cassandraMapping());
}
}
What are the things need to be done to get elasticsearch running?
JHipster can generate a Cassandra + Elasticsearch project -> from my understanding you didn't select that option when generating your project, but if you want to add Elasticsearch, just have a look at how we do it on a new project.
You could also re-generate your project, and add in your .yo-rc.json file:
"searchEngine": "elasticsearch"