Spring Data MongoDB Converter not getting registered - mongodb

I have a setup of multiple MongoDB configuration. Here is the configuration class
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(MongoConfigProperties.class)
public class MultipleMongoConfig {
private static final Logger logger = LoggerFactory.getLogger(MultipleMongoConfig.class);
private final MongoConfigProperties mongoProperties;
#Primary
#Bean(name = "sysdiagMongoTemplate")
public MongoOperations sysdiagMongoTemplate() {
MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(new DefaultDbRefResolver(sysdiagFactory(mongoProperties.getSysdiag())),
new MongoMappingContext());
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new AggregationResultReadConverter());
mappingMongoConverter.setCustomConversions(new CustomConversions(CustomConversions.StoreConversions.NONE, converters));
mappingMongoConverter.afterPropertiesSet();
boolean canConvert = mappingMongoConverter.getConversionService().canConvert(Document.class, AggregationResult.class);
mappingMongoConverter.afterPropertiesSet();
logger.info("canConvertFromDocumentToAggResult:: " + canConvert); //gives TRUE
return new MongoTemplate(sysdiagFactory(this.mongoProperties.getSysdiag()), mappingMongoConverter);
}
#Bean(name = "monitoringMongoTemplate")
public MongoOperations monitoringMongoTemplate() {
return new MongoTemplate(monitoringFactory(this.mongoProperties.getMonitoring()));
}
public MongoDbFactory sysdiagFactory(final MongoProperties mongo) {
return new SimpleMongoDbFactory(new MongoClient(mongo.getHost(), mongo.getPort()),
mongo.getDatabase());
}
public MongoDbFactory monitoringFactory(final MongoProperties mongo) {
return new SimpleMongoDbFactory(new MongoClient(mongo.getHost(), mongo.getPort()),
mongo.getDatabase());
}
}
Here is the read converter class (I only require reading from MongoDB). We have dynamic keys in the document due to which I need to convert them into a Map
public class AggregationResultReadConverter implements Converter<Document, AggregationResult> {
#Override
public AggregationResult convert(Document source) {
AggregationResult aggregationResult = new AggregationResult();
aggregationResult.setData(new HashMap());
for(Map.Entry<String,Object> entry : source.entrySet()){
if(entry.getKey().matches("[A-Z][A-Z][A-Z]")){
aggregationResult.getData().put(entry.getKey(), entry.getValue());
}
}
return aggregationResult;
}
}
Here is the mapping configuration for one of the MongoDB database
#Configuration
#EnableMongoRepositories(basePackages = {"com.hns.services.restapi.db.mongo.sysdiag.entity", "com.hns.services.restapi.db.mongo.sysdiag.repo"}, mongoTemplateRef = "sysdiagMongoTemplate")
public class SysdiagMongoConfig {
}
And here is the repository interface
#Repository
public interface AggregationResultRepository extends MongoRepository<AggregationResult, ObjectId> {
#Query("{ TIME: {$gte : ?0, $lt: ?1}}")
List<AggregationResult> findInTimeRange(Long startTime, Long endTime);
}
When I query using AggregationResultRepository, I expect the converter code to be executed so that I can convert the fields and put them in the Entity (Document) class object as per the logic. The query is going fine as I saw in the debug logs and I see an output but the converter is not getting called.
The converted is getting registered with the mongo template as the canConvertFromDocumentToAggResult logger gives TRUE. I tried changing the converted from Document -> AggregationResult to DBObject -> AggregationResult but no luck. Not sure what am I missing here.

Related

Spring Boot + Hibernate + JPA + Postgres Multi tenant App unable to persist entity

I am building a Multitenant saas application using single database with multiple schema; one schema per client. I am using Spring Boot 2.1.5, Hibernate 5.3.10 with compatible spring data jpa and postgres 11.2.
I have followed this blogpost https://dzone.com/articles/spring-boot-hibernate-multitenancy-implementation.
Tried debugging the code, below are my findings:
* For the default schema provided in the datasource configuration, hibernate properly validates schema. It creates the tables/entity in the default schema which are missing or new.
* Tenant Identifier is properly resolved and hibernate builds a session using this tenant.
I have uploaded the code in below repo :
https://github.com/naveentulsi/multitenant-lithium
Some important classes I have added here.
#Component
#Log4j2
public class MultiTenantConnectionProviderImpl implements
MultiTenantConnectionProvider {
#Autowired
DataSource dataSource;
#Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
if (!StringUtils.isEmpty(tenantIdentifier)) {
String setTenantQuery = String.format(AppConstants.SCHEMA_CHANGE_QUERY, tenantIdentifier);
connection.createStatement().execute(setTenantQuery);
final ResultSet resultSet = connection.createStatement().executeQuery("select current_schema()");
if(resultSet != null){
final String string = resultSet.getString(1);
log.info("Current Schema" + string);
}
System.out.println("Statement execution");
} else {
connection.createStatement().execute(String.format(AppConstants.SCHEMA_CHANGE_QUERY, AppConstants.DEFAULT_SCHEMA));
}
} catch (SQLException se) {
throw new HibernateException(
"Could not change schema for connection [" + tenantIdentifier + "]",
se
);
}
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
String Query = String.format(AppConstants.DEFAULT_SCHEMA, tenantIdentifier);
connection.createStatement().executeQuery(Query);
} catch (SQLException se) {
throw new HibernateException(
"Could not change schema for connection [" + tenantIdentifier + "]",
se
);
}
connection.close();
}
#Override
public boolean supportsAggressiveRelease() {
return true;
}
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
#Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
}
#Configuration
#EnableJpaRepositories
public class ApplicationConfiguration implements WebMvcConfigurer {
#Autowired
JpaProperties jpaProperties;
#Autowired
TenantInterceptor tenantInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tenantInterceptor);
}
#Bean
public DataSource dataSource() {
return DataSourceBuilder.create().username(AppConstants.USERNAME).password(AppConstants.PASS)
.url(AppConstants.URL)
.driverClassName("org.postgresql.Driver").build();
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, MultiTenantConnectionProviderImpl multiTenantConnectionProviderImpl, CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.ddl-auto", "update");
properties.put("hibernate.jdbc.lob.non_contextual_creation", "true");
properties.put("show-sql", "true");
properties.put("hikari.maximum-pool-size", "3");
properties.put("hibernate.default_schema", "master");
properties.put("maximum-pool-size", "2");
if (dataSource instanceof HikariDataSource) {
((HikariDataSource) dataSource).setMaximumPoolSize(3);
}
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
properties.put(Environment.FORMAT_SQL, true);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.saas");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
}
}
#Component
public class TenantResolver implements CurrentTenantIdentifierResolver {
private static final ThreadLocal<String> TENANT_IDENTIFIER = new ThreadLocal<>();
public static void setTenantIdentifier(String tenantIdentifier) {
TENANT_IDENTIFIER.set(tenantIdentifier);
}
public static void reset() {
TENANT_IDENTIFIER.remove();
}
#Override
public String resolveCurrentTenantIdentifier() {
String currentTenant = TENANT_IDENTIFIER.get() != null ? TENANT_IDENTIFIER.get() : AppConstants.DEFAULT_SCHEMA;
return currentTenant;
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
On successful injection of TenantId by the TenantResolver, the entityManager should be able to store the entities into the corresponding tenant schema in database. That is, if we create an object of an entity and persist same in db, it should be successfully saved in db. But in my case, entities are not getting saved into any schema other than the default one.
Update 1: I was able to do multi-tenant schema switching using mysql 8.0.12. Still not able to do it with postgres.
you should be using AbstractRoutingDataSource to achieve this, it does all the magic behind the scenes, there are many examples online and you can find one at https://www.baeldung.com/spring-abstract-routing-data-source
In your Class "ApplicationConfiguration.java";
You have to remove this "properties.put("hibernate.default_schema", "master");", Why because of when ever your changing the schema it's able to change but when it's reach this line again and again set the default schema
I hope you got the answer
Thank you all
Take care!

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!

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");
...
}

IndexMissingException: [News] missing

I have porject in Spring boot where i utilize elasticsearch. i save my data to my primary database in postgres and for searching in elasticsearch(ES). I have configured added some data. and i can see that there is my data in elasticsearch as well as in postgres. But when i try to run custom Query through searchQuery in returns my an error:
2016-12-09 20:03:09.586 ERROR 1704 --- [pool-2-thread-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
org.elasticsearch.indices.IndexMissingException: [provenNews] missing
at org.elasticsearch.cluster.metadata.MetaData.convertFromWildcards(MetaData.java:868)
at org.elasticsearch.cluster.metadata.MetaData.concreteIndices(MetaData.java:685)
at org.elasticsearch.action.search.type.TransportSearchTypeAction$BaseAsyncAction.<init>(TransportSearchTypeAction.java:113)
at org.elasticsearch.action.search.type.TransportSearchDfsQueryThenFetchAction$AsyncAction.<init>(TransportSearchDfsQueryThenFetchAction.java:75)
at org.elasticsearch.action.search.type.TransportSearchDfsQueryThenFetchAction$AsyncAction.<init>(TransportSearchDfsQueryThenFetchAction.java:68)
at org.elasticsearch.action.search.type.TransportSearchDfsQueryThenFetchAction.doExecute(TransportSearchDfsQueryThenFetchAction.java:65)
at org.elasticsearch.action.search.type.TransportSearchDfsQueryThenFetchAction.doExecute(TransportSearchDfsQueryThenFetchAction.java:55)
at org.elasticsearch.action.support.TransportAction.execute(TransportAction.java:75)
here is my code:
configuration class
#Configuration
#EnableTransactionManagement
#EnableElasticsearchRepositories(basePackages = "org.news.proven.repository")
public class ProjectConfiguration {
#Bean
public HibernateJpaSessionFactoryBean sessionFactory() {
return new HibernateJpaSessionFactoryBean();
}
#Bean
public ElasticsearchTemplate elasticsearchTemplate() {
return new ElasticsearchTemplate(getNodeClient());
}
private static NodeClient getNodeClient() {
return (NodeClient) nodeBuilder().clusterName(UUID.randomUUID().toString()).local(true).node()
.client();
}
}
my class that calls the searchQuery
#Service
public class NewsSearchServiceBean implements NewsSearchService {
#Autowired
private ProvenNewsRepository newsSearchRepository;
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public Page<ProvenNews> search(String query, int page)
{
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(QueryBuilders.multiMatchQuery(query)
.field("title", 0.6f) //boosting title
.field("newsText", 0.4f)
.type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
.slop(50)
.fuzziness(Fuzziness.ONE) //80 % of mispelling have an edit distance 1 (Damerau-Levenshtein edit distance)
)
.withPageable(new PageRequest(page, 15))
.build();
Page<ProvenNews> result = elasticsearchTemplate.queryForPage(searchQuery, ProvenNews.class);//newsSearchRepository.search(searchQuery);
return result;
// return newsSearchRepository.findByNewsTextAndTitle(query,query,new PageRequest(page, 10, Direction.DESC, "newsDate"));
}
MY repositoy:
public interface ProvenNewsRepository extends ElasticsearchCrudRepository<ProvenNews, Long> {
public Page<ProvenNews> findByNewsTextAndTitle(String newsText, String Title, Pageable page);
}
Any advise and assist will be appreciated)

Spring Java config message convertor priority

I have defined two convertors like this using Spring Java config. I always get a XML response unless I specified the 'Accept=applicaiton/json' in the HTTP header. Is there a way to set the default convertor to be JSON instead of XML convertor.
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = {"foo.bar"})
public class WebMvcConfig extends WebMvcConfigurerAdapter {
...
#Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
#Bean
public MappingJackson2XmlHttpMessageConverter xmlConverter() {
MappingJackson2XmlHttpMessageConverter xmlConverter = new MappingJackson2XmlHttpMessageConverter();
return xmlConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jsonConverter());
converters.add(xmlConverter());
super.configureMessageConverters(converters);
}
Here is my controller.
#RequestMapping(value = "/product")
public
#ResponseBody
BSONObject getProducts(#RequestParam String ids,
#RequestParam(required = false) String types) {
List<BSONObject> products = commonDataService.getData(ids, types);
return products;
}
Try the following configuration, it sets up the default Content negotiation strategy(based on article here):
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
Another option will be to provide other ways of specifying the content format, if Accept header is not feasible, an option could be to specify an extension /myuri/sample.json which would be returned as a json.