NotifierConfiguration example in 2.1.4 SBA documentation - spring-boot-admin

I am trying to implement RemindingNotifier in my Spring Boot Admin application but code snippet given in documentation uses a "repository" variable which is not declared anywhere. How to get this variable?
Code Snippet
#Configuration
public class NotifierConfiguration {
#Autowired
private Notifier notifier;
#Primary
#Bean(initMethod = "start", destroyMethod = "stop")
public RemindingNotifier remindingNotifier() {
RemindingNotifier notifier = new RemindingNotifier(notifier, repository);
notifier.setReminderPeriod(Duration.ofMinutes(10));
notifier.setCheckReminderInverval(Duration.ofSeconds(10));
return notifier;
}
}

So i was able to solve this by using this from SBA documentation.
#Configuration
public static class NotifierConfig {
private final InstanceRepository repository;
private final ObjectProvider<List<Notifier>> otherNotifiers;
public NotifierConfig(InstanceRepository repository, ObjectProvider<List<Notifier>> otherNotifiers) {
this.repository = repository;
this.otherNotifiers = otherNotifiers;
}
#Bean
public FilteringNotifier filteringNotifier() {
CompositeNotifier delegate = new CompositeNotifier(otherNotifiers.getIfAvailable(Collections::emptyList));
return new FilteringNotifier(delegate, repository);
}
#Primary
#Bean(initMethod = "start", destroyMethod = "stop")
public RemindingNotifier remindingNotifier() {
RemindingNotifier notifier = new RemindingNotifier(filteringNotifier(), repository);
notifier.setReminderPeriod(Duration.ofMinutes(10));
notifier.setCheckReminderInverval(Duration.ofSeconds(10));
return notifier;
}
}

Related

Configuration for Mvc testing

guys. I have spring MVC project and I want to test CoursesController, but can not find out how to do it.
Do I need to make separated configuration class for tests?
Before springMvc I used separated configuration class for test with embedded database.
I'd appreciate all the help I can get.
CoursesController class:
#Controller
#RequestMapping("/courses")
public class CoursesController {
private final CourseService courseService;
#Autowired
public CoursesController(CourseService courseService) {
this.courseService = courseService;
}
#GetMapping()
public String index(Model model, #RequestParam("page") Optional<Integer> page,
#RequestParam("size") Optional<Integer> size) throws ServiceException {
int currentPage = page.orElse(1);
int pageSize = size.orElse(10);
Page<Course> coursePage = courseService.findPaginated(PageRequest.of(currentPage - 1, pageSize));
model.addAttribute("coursePage", coursePage);
int totalPages = coursePage.getTotalPages();
if (totalPages > 0) {
List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages).boxed().collect(Collectors.toList());
model.addAttribute("pageNumbers", pageNumbers);
}
return "courses/index";
}
}
Configuration class:
#Configuration
#ComponentScan("com.university")
#PropertySource("classpath:/application.properties")
#EnableWebMvc
public class Config implements WebMvcConfigurer {
#Autowired
private Environment env;
private final ApplicationContext applicationContext;
#Autowired
public Config(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Bean
public HikariDataSource dataSource() {
return (HikariDataSource) DataSourceBuilder.create().type(HikariDataSource.class)
.url(env.getProperty("spring.datasource.url"))
.driverClassName(env.getProperty("spring.datasource.driverClassName"))
.username(env.getProperty("spring.datasource.username"))
.password(env.getProperty("spring.datasource.password")).build();
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
registry.viewResolver(resolver);
}
#Bean
public SessionLocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH);
return localeResolver;
}
}
I did separate configuration class for tests with H2 database.

Multi-Tenancy in Reactive Spring boot application using mongodb-reactive

How can we create a multi-tenant application in spring webflux using Mongodb-reactive repository?
I cannot find any complete resources on the web for reactive applications. all the resources available are for non-reactive applications.
UPDATE:
In a non-reactive application, we used to store contextual data in ThreadLocal but this cannot be done with reactive applications as there is thread switching. There is a way to store contextual info in reactor Context inside a WebFilter, But I don't how get hold of that data in ReactiveMongoDatabaseFactory class.
Thanks.
I was able to Implement Multi-Tenancy in Spring Reactive application using mangodb. Main classes responsible for realizing were: Custom MongoDbFactory class, WebFilter class (instead of Servlet Filter) for capturing tenant info and a ThreadLocal class for storing tenant info. Flow is very simple:
Capture Tenant related info from the request in WebFilter and set it in ThreadLocal. Here I am sending Tenant info using header: X-Tenant
Implement Custom MondoDbFactory class and override getMongoDatabase() method to return database based on current tenant available in ThreadLocal class.
Source code is:
CurrentTenantHolder.java
package com.jazasoft.demo;
public class CurrentTenantHolder {
private static final ThreadLocal<String> currentTenant = new InheritableThreadLocal<>();
public static String get() {
return currentTenant.get();
}
public static void set(String tenant) {
currentTenant.set(tenant);
}
public static String remove() {
synchronized (currentTenant) {
String tenant = currentTenant.get();
currentTenant.remove();
return tenant;
}
}
}
TenantContextWebFilter.java
package com.example.demo;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
#Component
public class TenantContextWebFilter implements WebFilter {
public static final String TENANT_HTTP_HEADER = "X-Tenant";
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (request.getHeaders().containsKey(TENANT_HTTP_HEADER)) {
String tenant = request.getHeaders().getFirst(TENANT_HTTP_HEADER);
CurrentTenantHolder.set(tenant);
}
return chain.filter(exchange).doOnSuccessOrError((Void v, Throwable throwable) -> CurrentTenantHolder.remove());
}
}
MultiTenantMongoDbFactory.java
package com.example.demo;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoDatabase;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
public class MultiTenantMongoDbFactory extends SimpleReactiveMongoDatabaseFactory {
private final String defaultDatabase;
public MultiTenantMongoDbFactory(MongoClient mongoClient, String databaseName) {
super(mongoClient, databaseName);
this.defaultDatabase = databaseName;
}
#Override
public MongoDatabase getMongoDatabase() throws DataAccessException {
final String tlName = CurrentTenantHolder.get();
final String dbToUse = (tlName != null ? tlName : this.defaultDatabase);
return super.getMongoDatabase(dbToUse);
}
}
MongoDbConfig.java
package com.example.demo;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.ReactiveMongoClientFactoryBean;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
#Configuration
public class MongoDbConfig {
#Bean
public ReactiveMongoTemplate reactiveMongoTemplate(MultiTenantMongoDbFactory multiTenantMongoDbFactory) {
return new ReactiveMongoTemplate(multiTenantMongoDbFactory);
}
#Bean
public MultiTenantMongoDbFactory multiTenantMangoDbFactory(MongoClient mongoClient) {
return new MultiTenantMongoDbFactory(mongoClient, "test1");
}
#Bean
public ReactiveMongoClientFactoryBean mongoClient() {
ReactiveMongoClientFactoryBean clientFactory = new ReactiveMongoClientFactoryBean();
clientFactory.setHost("localhost");
return clientFactory;
}
}
UPDATE:
In reactive-stream we cannot store contextual information in ThreadLocal any more as the request is not tied to a single thread, So, This is not the correct solution.
However, Contextual information can be stored reactor Context in WebFilter like this. chain.filter(exchange).subscriberContext(context -> context.put("tenant", tenant));. Problem is how do get hold of this contextual info in ReactiveMongoDatabaseFactory implementation class.
Here is my very rough working solution for Spring WebFlux - they have since updated the ReactiveMongoDatabaseFactory - getMongoDatabase to return a Mono
Create web filter
public class TenantContextFilter implements WebFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(TenantContextFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange swe, WebFilterChain wfc) {
ServerHttpRequest request = swe.getRequest();
HttpHeaders headers = request.getHeaders();
if(headers.getFirst("X-TENANT-ID") == null){
LOGGER.info(String.format("Missing X-TENANT-ID header"));
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
String tenantId = headers.getFirst("X-TENANT-ID");
LOGGER.info(String.format("Processing request with tenant identifier [%s]", tenantId));
return wfc.filter(swe)
.contextWrite(TenantContextHolder.setTenantId(tenantId));
}
}
Create class to get context (credit to somewhere I found this)
public class TenantContextHolder {
public static final String TENANT_ID = TenantContextHolder.class.getName() + ".TENANT_ID";
public static Context setTenantId(String id) {
return Context.of(TENANT_ID, Mono.just(id));
}
public static Mono<String> getTenantId() {
return Mono.deferContextual(contextView -> {
if (contextView.hasKey(TENANT_ID)) {
return contextView.get(TENANT_ID);
}
return Mono.empty();
}
);
}
public static Function<Context, Context> clearContext() {
return (context) -> context.delete(TENANT_ID);
}
}
My spring security setup (all requests allowed for testing)
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain WebFilterChain(ServerHttpSecurity http) {
return http
.formLogin(it -> it.disable())
.cors(it -> it.disable()) //fix this
.httpBasic(it -> it.disable())
.csrf(it -> it.disable())
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.authorizeExchange(it -> it.anyExchange().permitAll()) //allow anonymous
.addFilterAt(new TenantContextFilter(), SecurityWebFiltersOrder.HTTP_BASIC)
.build();
}
}
Create Tenant Mongo DB Factory
I still have some clean-up work for defaults etc...
public class MultiTenantMongoDBFactory extends SimpleReactiveMongoDatabaseFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(MultiTenantMongoDBFactory.class);
private final String defaultDb;
public MultiTenantMongoDBFactory(MongoClient mongoClient, String databaseName) {
super(mongoClient, databaseName);
this.defaultDb = databaseName;
}
#Override
public Mono<MongoDatabase> getMongoDatabase() throws DataAccessException {
return TenantContextHolder.getTenantId()
.map(id -> {
LOGGER.info(String.format("Database trying to retrieved is [%s]", id));
return super.getMongoDatabase(id);
})
.flatMap(db -> {
return db;
})
.log();
}
}
Configuration Class
#Configuration
#EnableReactiveMongoAuditing
#EnableReactiveMongoRepositories(basePackages = {"com.order.repository"})
class MongoDbConfiguration {
#Bean
public ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory() {
return new MultiTenantMongoDBFactory(MongoClients.create("mongodb://user:password#localhost:27017"), "tenant_catalog");
}
#Bean
public ReactiveMongoTemplate reactiveMongoTemplate() {
ReactiveMongoTemplate template = new ReactiveMongoTemplate(reactiveMongoDatabaseFactory());
template.setWriteResultChecking(WriteResultChecking.EXCEPTION);
return template;
}
}
Entity Class
#Document(collection = "order")
//getters
//setters
Testing
Create two mongo db's with same collection, put different documents in both
In Postman I just did a get request with the "X-TENANT-ID" header and database name as the value (e.g. tenant-12343 or tenant-34383) and good to go!

Spring Boot : Apache CXF SOAP with #RestController

I am making Spring Boot rest service using #RestController, in same project I am also exposing the Apache CXF SOAP service like
#RestController Code
#RestController
#RequestMapping(value = "/mobileTopUpService")
public class TopUpRestService {
#RequestMapping(value="/processTopUpRequest", method=RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<TopUpRequestDTO> processTopUpRequest(HttpServletRequest httpServletRequest, #Valid RequestEntity<TopUpRequestDTO> _requestEntity) {
return new ResponseEntity<>(new exampleDTO("hi"), HttpStatus.OK);
}
}
Apache CXF SOAP
#Configuration
#Import(ApplicationConfiguration.class)
public class WebServiceConfig
{
public static final String SERVLET_MAPPING_URL_PATH = "/*";
public static final String SERVICE_NAME_URL_PATH = "/services";
#Autowired
private ApplicationConfiguration applicationConfiguration;
#Bean
public ServletRegistrationBean dispatcherServlet()
{
return new ServletRegistrationBean(new CXFServlet(), SERVLET_MAPPING_URL_PATH);
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus()
{
return new SpringBus();
}
#Bean
public ERSBackendService ersBackendServiceImpl()
{
return new ERSBackendServiceImpl();
}
#Bean
public Endpoint endpoint()
{
EndpointImpl endpoint = new EndpointImpl(springBus(), ersBackendServiceImpl());
endpoint.publish(SERVICE_NAME_URL_PATH);
AutomaticWorkQueue executorQueue = createThreadPoolExecutorQueue();
endpoint.setExecutor(executorQueue);
return endpoint;
}
#Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory()
{
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory("/backend-service", Integer.valueOf(applicationConfiguration.getPort()));
return factory;
}
}
SOAP Service are running fine after change but REST (#RestController) stop working, but if I disables the methods
// #Bean
// public ServletRegistrationBean dispatcherServlet()
// {
// return new ServletRegistrationBean(new CXFServlet(), SERVLET_MAPPING_URL_PATH);
// }
and
#Bean
// public EmbeddedServletContainerFactory embeddedServletContainerFactory()
// {
// TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory("/backend-service", Integer.valueOf("8007"));
// return factory;
// }
//}
and run
http://localhost:8007/mobileTopUpService/processTopUpRequest/
the #RestController runs fine but not soap.
I need to run both #RestController and CXF SOAP, kindly suggest.
thanks
I've just working with SOAP and REST servicies together. Here's my configuration: (At the end of the answer, I included a sample project)
application.properties
cxf.path=/services
cxf.servlet.load-on-startup=-1
WebServiceConfig
#Configuration
#ConditionalOnWebApplication
public class WebServiceConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(WsEndpointsConfiguration.class);
#Autowired
private Bus bus;
#Value("${cxf.path}")
private String cxfServletPath;
#Autowired
private YourServiceInterface yourService;
public Logger getLOGGER() {
return LOGGER;
}
public Bus getBus() {
return bus;
}
public String getCxfServletPath() {
return cxfServletPath;
}
public void setCxfServletPath(String cxfServletPath) {
this.cxfServletPath = cxfServletPath;
}
public YourServiceInterface getYourServiceInterface() {
return yourService;
}
#Bean
public Endpoint yourWebServiceEndpoint() {
EndpointImpl endpoint = new EndpointImpl(getBus(), new YourWebServiceEndpoint(getYourServiceInterface()));
endpoint.publish("/YourWebService");
return endpoint;
}
#Bean
public FilterRegistrationBean openEntityManagerInViewFilter() {
FilterRegistrationBean filterRegBean = new FilterRegistrationBean();
filterRegBean.setFilter(new OpenEntityManagerInViewFilter());
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add(getCxfServletPath() + "/*");
filterRegBean.setUrlPatterns(urlPatterns);
if (getLOGGER().isDebugEnabled()) {
getLOGGER().debug("Registering the 'OpenEntityManagerInViewFilter' filter for the '"
.concat(getCxfServletPath() + "/*").concat("' URL."));
}
return filterRegBean;
}
}
Replace the #Autowired service with your own service interface.
You could check a full example here:
https://github.com/jcagarcia/proofs/tree/master/spring-security-and-formatters
Related classes from the sample provided above:
Configuration class
WebService API
WebService Implementation
application.properties
Hope it helps,
I resolved it as #EnableWebMvc in class where starting boot app
i.e SpringApplication.run(ApplicationStartup.class, args);
Moved ServletRegistrationBean in spring boot class too,
disbaled method
#Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {...}

How to run update query in Spring JPA for quartz job

I have a quartz job in spring 4 and I am using JPA hibernate to update database value through quartz job but I am getting javax.persistence.TransactionRequiredException: Executing an update/delete query
I don't understand what kind of configuration is missing in quartz job. I referred to SpringBeanAutowiringSupport example still update is failing but select is working fine.
Below is my code
#Configuration
#ComponentScan("com.stock")
public class QuartzConfiguration {
#Autowired
private ApplicationContext applicationContext;
#Bean
public JobDetailFactoryBean jobDetailBalanceCarryForward(){
JobDetailFactoryBean factory = new JobDetailFactoryBean();
factory.setJobClass(BillingCroneSvcImpl.class);
Map<String,Object> map = new HashMap<String,Object>();
map.put("task", "balanceCarryForward");
factory.setJobDataAsMap(map);
factory.setGroup("BalanceCarryForwardJob");
factory.setName("balance carry forward");
return factory;
}
#Bean
public CronTriggerFactoryBean cronTriggerBalanceCarryForward(){
CronTriggerFactoryBean stFactory = new CronTriggerFactoryBean();
stFactory.setJobDetail(jobDetailBalanceCarryForward().getObject());
stFactory.setStartDelay(3000);
stFactory.setName("balancCarryForwardTrigger");
stFactory.setGroup("balanceCarryForwardgroup");
stFactory.setCronExpression("0 0/1 * 1/1 * ? *");
return stFactory;
}
#Bean
public SpringBeanJobFactory springBeanJobFactory() {
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
#Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
schedulerFactory.setJobFactory(springBeanJobFactory());
schedulerFactory.setTriggers(cronTriggerBalanceCarryForward().getObject());
return schedulerFactory;
}
}
Below class where quartz executeInternal method is written
#Service
#PersistJobDataAfterExecution
#DisallowConcurrentExecution
#Autowired
private BillingCroneRepo billingCroneRepo;
public class BillingCroneSvcImpl extends QuartzJobBean implements BillingCroneSvc {
#Override
#Transactional
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(context);
billingCroneRepo.updateBalance();
// this method throws exception javax.persistence.TransactionRequiredException: Executing an update/delete query
}
}
App config class
#EnableWebMvc
#EnableTransactionManagement
#Configuration
#ComponentScan({ "com.stock.*" })
#Import({ SecurityConfig.class })
#PropertySource("classpath:jdbc.properties")
public class AppConfig extends WebMvcConfigurerAdapter {
private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";
#Resource
private Environment env;
#Bean(name = "dataSource")
public DriverManagerDataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
driverManagerDataSource.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
driverManagerDataSource.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
driverManagerDataSource.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
return driverManagerDataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
entityManagerFactoryBean.setJpaProperties(hibProperties());
return entityManagerFactoryBean;
}
private Properties hibProperties() {
Properties properties = new Properties();
properties.put(PROPERTY_NAME_HIBERNATE_DIALECT,env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL,env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
return properties;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
#Bean
public ReloadableResourceBundleMessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource=new ReloadableResourceBundleMessageSource();
String[] resources= {"classpath:messages"};
messageSource.setBasenames(resources);
return messageSource;
}
#Bean
public LocaleResolver localeResolver() {
final CookieLocaleResolver ret = new CookieLocaleResolver();
ret.setDefaultLocale(new Locale("en_IN"));
return ret;
}
#Bean
public LocaleChangeInterceptor localeChangeInterceptor(){
LocaleChangeInterceptor localeChangeInterceptor=new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("language");
return localeChangeInterceptor;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/Angular/**").addResourceLocations("/Angular/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/email_templates/**").addResourceLocations("/email_templates/");
registry.addResourceHandler("/fonts/**").addResourceLocations("/fonts/");
registry.addResourceHandler("/img/**").addResourceLocations("/img/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/Landing_page/**").addResourceLocations("/Landing_page/");
}
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
org.springframework.core.io.Resource[] resources = new ClassPathResource[] { new ClassPathResource("application.properties") };
pspc.setLocations(resources);
pspc.setIgnoreUnresolvablePlaceholders(true);
return pspc;
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
// through below code we directly read properties file in jsp file
#Bean(name = "propertyConfigurer")
public PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("application.properties"));
return bean;
}
}
Can anybody please assist me how to resolve transational issue in spring JPA with quartz
Thanks you all for your help. Finally I autowired EntityManagerFactory instead of persitance EntityManager and it is working fine. I tried all scenario but nothing worked to inject spring transactional in quartz so finally autoriwed entitymanagerfactory
Below is my repo class code.
#Repository
public class BillingCroneRepoImpl implements BillingCroneRepo {
/*#PersistenceContext
private EntityManager entityManager;*/
#Autowired
EntityManagerFactory entityManagerFactory;
public boolean updateTable(){
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin(); // this will go in try catch
Query query = entityManager.createQuery(updateSql);
// update table code goes here
entityTransaction.commit(); // this will go in try catch
}
}
I'm not the Spring specialist, but I think new ... doesn't work with #Transactional
#Service
#PersistJobDataAfterExecution
#DisallowConcurrentExecution
public class BillingCroneSvcImpl extends QuartzJobBean implements BillingCroneSvc {
#Autowired
BillingCroneRepo billingCroneRepo;
#Override
#Transactional
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(context);
billingCroneRepo.updateBalance();
}
}
It's because quartz is using the bean instead of the proxy generated for #Transactional.
Use either MethodInvokingJobDetailFactoryBean (instead of inheriting QuartzJob) or use a dedicated wrapper quarz bean (inheriting from QuartzJob) that call the spring bean (not inheriting from QuartzJob) having the #Transactionnal annotation.
EDIT : this is in fact not the problem
The problem is here :
JobDetailFactoryBean factory = new JobDetailFactoryBean();
factory.setJobClass(BillingCroneSvcImpl.class);
By passing the class, I presume that Quartz will instantiate it itself, so Spring won't create it and won't wrap the bean in a Proxy that handle the #Transactionnal behaviour.
Instead you must use something along the line :
#Bean(name = "billingCroneSvc")
public BillingCroneSvc getSvc(){
return new BillingCroneSvcImpl();
}
#Bean
public JobDetailFactoryBean jobDetailBalanceCarryForward(){
JobDetailFactoryBean factory = new JobDetailFactoryBean();
getSvc();// just make sure the bean is instantiated
factory.setBeanName("billingCroneSvc");
...
}

Spring-boot jpa only can access primary from muliple data sources

Trying to use 2 data sources as below inside spring-boot:
DB configs:
//no primary
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "rwEntityManagerFactory",
transactionManagerRef = "rwTransactionManager",
basePackages = {"com.cvs.dvp.repository.rw"})
#PropertySource("classpath:application.properties")
public class RwDbConfiguration {
#Bean(name="rwDataSource")
//#Primary
#ConfigurationProperties(prefix="datasource.rw")
public DataSource rwDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name="rwEntityManagerFactory")
#Qualifier("rwEntityManagerFactory")
public EntityManagerFactory rwEntityManagerFactory() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setShowSql(true);
jpaVendorAdapter.setGenerateDdl(false);
jpaVendorAdapter.setDatabase(Database.ORACLE);
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(rwDataSource());
lef.setJpaVendorAdapter(jpaVendorAdapter);
lef.setPackagesToScan("com.cvs.dvp.domain.rw");
lef.setPersistenceUnitName("rw");
lef.afterPropertiesSet();
return lef.getObject();
}
#Bean(name="rwTransactionManager")
#Qualifier("rwTransactionManager")
public JpaTransactionManager transactionManager(#Qualifier("rwEntityManagerFactory")EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
And //primary
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "dsEntityManagerFactory",
transactionManagerRef = "dsTransactionManager",
basePackages = {"com.cvs.dvp.repository.dsd"})
#PropertySource("classpath:application.properties")
public class DsdDbConfiguration {
#Bean(name="dsDataSource")
#Primary
#ConfigurationProperties(prefix="datasource.ds")
public DataSource dsDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name="dsEntityManagerFactory")
#Qualifier("dsEntityManagerFactory")
public EntityManagerFactory dsEntityManagerFactory( ) {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setShowSql(true);
jpaVendorAdapter.setGenerateDdl(true);
jpaVendorAdapter.setDatabase(Database.ORACLE);
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dsDataSource());
lef.setJpaVendorAdapter(jpaVendorAdapter);
lef.setPackagesToScan("com.cvs.dvp.domain.dsd");
lef.setPersistenceUnitName("dsd");
lef.afterPropertiesSet();
return lef.getObject();
}
#Bean(name="dsTransactionManager")
#Qualifier("dsTransactionManager")
#Primary
public JpaTransactionManager transactionManager(#Qualifier("dsEntityManagerFactory")EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
The services are:
#Service
public class ReceiptServiceImpl implements ReceiptService {
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiptServiceImpl.class);
private final ReceiptRepository repository;
#Autowired
#PersistenceContext(unitName = "rw")
#Qualifier("rwTransactionManager")
private JpaTransactionManager jpaTransactionManager;
#Inject
public ReceiptServiceImpl(final ReceiptRepository repository) {
this.repository = repository;
}
#Override
#Transactional("rwTransactionManager")
public Receipt save(#NotNull #Valid final Receipt receipt) {
EntityManagerFactory emf = jpaTransactionManager.getEntityManagerFactory();
EntityManager entityManager = emf.createEntityManager();
LOGGER.debug("Creating {}", receipt.getId());
Receipt existing = repository.findOne(receipt.getStoreNumber());
if (existing != null) {
throw new UserAlreadyExistsException(
String.format("There already exists a receipt with id=%s", receipt.getId()));
}
return repository.save(existing);
}
#Override
#Transactional("rwTransactionManager")
public List<Receipt> getList() {
LOGGER.debug("Retrieving the list of all users");
return repository.findAll();
}
#Override
#Transactional("rwTransactionManager")
public Receipt getOne(String storeNumber) {
return repository.findOne(storeNumber);
}
}
And
#Service
#Validated
public class StatusServiceImpl implements StatusService {
private static final Logger LOGGER = LoggerFactory.getLogger(StatusServiceImpl.class);
private final StatusRepository repository;
#Autowired
#Qualifier("dsTransactionManager")
private JpaTransactionManager jpaTransactionManager;
#Inject
public StatusServiceImpl(final StatusRepository repository) {
this.repository = repository;
}
#Override
#Transactional
public Status save(#NotNull #Valid final Status status) {
EntityManagerFactory emf = jpaTransactionManager.getEntityManagerFactory();
EntityManager entityManager = emf.createEntityManager();
LOGGER.debug("Creating {}", status);
Status existing = repository.findOne(status.getStatusCd());
if (existing != null) {
throw new UserAlreadyExistsException(
String.format("There already exists a user with id=%s", status.getStatusCd()));
}
return repository.save(status);
}
#Override
#Transactional
public List<Status> getList() {
LOGGER.debug("Retrieving the list of all users");
return repository.findAll();
}
}
The issue: bean wiring is ok, but only primary datasource can be accessed, another datasource got "table or view does not existed" error, but if it been configed as primary everything works fine and previous primary datasource will not be accessable.
Tried to make none of them primary as post Multiple jpa:repositories in xml config, how to configure with #EnableJPARepositories using Spring java config?, but would not pass spring-boot compiler.
Spring CLI version is 1.2.1.
Appreciate your help.