Custom Hibernate Validator Spring Boot - java

I wish to use the Java 8 ReflectionParameterNameProvider Hibernate Validator to return proper parameter names instead of .argN e.g. getPerson.arg0
I am compiling the application with the -parameter flag and have added the following Bean to my context:
#Bean
public javax.validation.Validator validator() {
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.parameterNameProvider(new ReflectionParameterNameProvider())
.buildValidatorFactory();
return validatorFactory.getValidator();
}
But am still getting the old getPerson.arg0
Any ideas, Thanks?

If you are relying on Hibernate Validator's integration with Hibernate to perform the validation, I found that it was necessary to put all my configuration inside validation.xml and let Hibernate Validator bootstrap from that configuration instead.
The configuration you've specified will work for spring-specific things, but won't work for the Hibernate/Hibernate Validator integration unfortunately.

Figured it out, just had to set this in the methodValidationPostProcessor bean.
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
#Bean
public Validator validator() {
final ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.parameterNameProvider(new ReflectionParameterNameProvider())
.buildValidatorFactory();
return validatorFactory.getValidator();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
final MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}

Another solution, that also keeps all Spring defaults untouched, is to override method postProcessConfiguration():
#Bean
static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean() {
#Override
protected void postProcessConfiguration(
javax.validation.Configuration<?> configuration) {
configuration.parameterNameProvider(new ReflectionParameterNameProvider());
}
};
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}

Related

How should I register custom Hibernate 5 data type (BasicType) when Spring Data is used?

I use Spring Data and decided that I want to create new custom data type that can be used in Hibernate entities. I checked the documentation and choose BasicType and implemented it according to this official user guide.
I wanted to be able to register the type under its class name and be able to use the new type in entities without need for #Type annotation. Unfortunately, I’m unable to get reference to the MetadataBuilder or Hibernate configuration to register the new type. Is there a way how to get it in Spring Data? It seems that initialization of the Hibernate is hidden from the user and cannot be easily accessed. We use following class to initialize the JPA:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager",
basePackages = {
"..." // omitted
}
)
public class JpaConfiguration implements TransactionManagementConfigurer {
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean configureEntityManagerFactory(
DataSource dataSource,
SchemaPerTenantConnectionProviderImpl provider) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPersistenceUnitName("defaultPersistenceUnit");
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setPackagesToScan(
"..." // omitted
);
entityManagerFactoryBean.setJpaProperties(properties(provider));
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return entityManagerFactoryBean;
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new JpaTransactionManager();
}
private Properties properties(SchemaPerTenantConnectionProviderImpl provider) {
Properties properties = new Properties();
// omitted
return properties;
}
}
I found lots of articles about way how to do it with Hibernate’s Configuration object but this one refers to Hibernate 3 and 4. I also found way how to do it via Hibernate org.hibernate.integrator.spi.Integrator but when I use it according to the articles I found I will get exception with the message “org.hibernate.HibernateException: Can not alter TypeRegistry at this time”
What is the correct way to register custom types in Spring Data?
I finally figured it out. I will post it here for others:
I created a new class that implements org.hibernate.boot.spi.SessionFactoryBuilderFactory interface. In this class I can get reference to the TypeResolver from metadata and register my custom type.
package com.example.configuration;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryBuilderFactory;
import org.hibernate.boot.spi.SessionFactoryBuilderImplementor;
import org.slf4j.LoggerFactory;
import com.example.CustomType;
public class CustomDataTypesRegistration implements SessionFactoryBuilderFactory {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(CustomDataTypesRegistration.class);
#Override
public SessionFactoryBuilder getSessionFactoryBuilder(final MetadataImplementor metadata, final SessionFactoryBuilderImplementor defaultBuilder) {
logger.info("Registering custom Hibernate data types");
metadata.getTypeResolver().registerTypeOverride(CustomType.INSTANCE);
return defaultBuilder;
}
}
The class must be then registered via Java ServiceLoader mechanism by adding full name of the class with its packages into the file with name org.hibernate.boot.spi.SessionFactoryBuilderFactory into the java module’s META-INF/services directory:
src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory
The file can contain multiple lines, each referencing different class. In this case it is:
com.example.configuration.CustomDataTypesRegistration
This way the Spring Data starts and custom type is successfully registered during Hibernate initialization.
What helped my quite a lot was this SO answer that deals with schema export in Hibernate 5 under Spring Data.
There's a much easier solution to this -- in fact, it's just 1 line of code. You can just use the #TypeDef annotation and thus avoid having to register the custom type:
#Entity(name = "Product")
#TypeDef(
name = "bitset",
defaultForType = BitSet.class,
typeClass = BitSetType.class
)
public static class Product {
#Id
private Integer id;
private BitSet bitSet;
For an example, see "Example 11. Using #TypeDef to register a custom Type" in http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html
I use JPA with Spring 4.3.9 and Hibernate 5.0.5 and I use custom property EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS with Spring
LocalContainerEntityManagerFactoryBean to override Hibernate BasicTypes.
final Properties jpaProperties = new Properties();
jpaProperties.put(EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS, new TypeContributorList() {
#Override
public List<TypeContributor> getTypeContributors() {
return Lists.newArrayList(new CustomDateTimeTypeContributor());
}
});
final LocalContainerEntityManagerFactoryBean factoryBean = new
LocalContainerEntityManagerFactoryBean();
factoryBean.setJpaProperties(jpaProperties);
factoryBean.setJpaVendorAdapter(jpaVendorAdapter);
return factoryBean;
An alternative to what xMort did, can be registering a org.hibernate.boot.model.TypeContributor via ServiceLoader mechanism.
Implement TypeContributor
package com.example.configuration;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.service.ServiceRegistry;
public class CustomTypeContributor implements TypeContributor {
#Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
typeContributions.contributeType(CustomType.INSTANCE);
}
}
Create a file org.hibernate.boot.model.TypeContributor into the java module’s META-INF/services
src/main/resources/META-INF/services/org.hibernate.boot.model.TypeContributor
Reference the TypeContributor, file content:
ru.eastbanctech.scs.air.transaction.repositories.StringArrayTypeContributor
As of Hibernate 5.2.17, the code that picks up the TypeContributor service can be found at org.hibernate.boot.model.process.spi.MetadataBuildingProcess#handleTypes.
Inspired by #alex.tran's answer:
#Bean
public HibernatePropertiesCustomizer customHibernateTypeRegistrar() {
return (Map<String, Object> props) -> {
props.put(
EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS,
(TypeContributorList) () -> Arrays.asList((TypeContributor) (typeContributions, serviceRegistry) -> {
// Deregister built-in org.hibernate.type.OffsetDateTimeSingleColumnType as it hides our mapping.
final BasicTypeRegistry basicTypeRegistry = typeContributions.getTypeConfiguration().getBasicTypeRegistry();
Class<OffsetDateTime> clazz = OffsetDateTime.class;
basicTypeRegistry.unregister(clazz.getName());
basicTypeRegistry.unregister(clazz.getSimpleName());
typeContributions.contributeSqlTypeDescriptor(OffsetDateTimeMsSqlTypeDescriptor.INSTANCE);
typeContributions.contributeJavaTypeDescriptor(OffsetDateTimeJavaTypeDescriptor.INSTANCE);
typeContributions.contributeType(OffsetDateTimeSingleColumnType.INSTANCE);
}));
};
}
Magical constant EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS & bean of type HibernatePropertiesCustomizer do the magic in EntityManagerFactoryBuilderImpl:
HibernateJpaAutoConfiguration -> HibernateJpaConfiguration -> JpaBaseConfiguration -> LocalContainerEntityManagerFactoryBean -> HibernatePersistenceProvider -> Bootstrap.getEntityManagerFactoryBuilder() -> EntityManagerFactoryBuilderImpl.
See details: https://discourse.hibernate.org/t/map-ms-sql-server-datetimeoffset-to-java-8-offsetdatetime/5937/7
Solution keeps Boot magic intact as it plugs into Spring Boot autoconfiguration, also there is no need to redefine lots of beans (like transaction or entity manager).
You can use the HibernatePropertiesCustomizer class spring will load all beans of this type (for more details see HibernateJpaConfiguration)
Also see PG Json config Test
this is an example (I'm adding Json type only in case postgress dialect is configured)
#Bean
#ConditionalOnProperty(value = "spring.jpa.properties.hibernate.dialect",
havingValue = "org.hibernate.dialect.PostgreSQL10Dialect")
public HibernatePropertiesCustomizer hibernatePropertiesCustomizerPG(ObjectMapper objectMapper) {
return hibernateProperties -> {
hibernateProperties.put(EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS,
(TypeContributorList) () -> List.of(
(TypeContributor) (TypeContributions typeContributions, ServiceRegistry serviceRegistry) ->
typeContributions.contributeType(new JsonType(objectMapper))));
};
}
This is the same than have
#TypeDefs({
#TypeDef(name = "json", typeClass = JsonType.class)
})
#MappedSuperclass
public class BaseEntity {
//Code omitted for brevity
}
I prefer to do in this way to not override any bean handled by spring such as SessionFactory
There is another solution.
We extend Hibernate MetadaSources overriding methods getMetadataBuilder by creating MetadataBuilder:
import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.internal.MetadataBuilderImpl;
import org.hibernate.boot.registry.StandardServiceRegistry;
public class CustomTypeIncludedMetadataSources extends MetadataSources {
#Override
public MetadataBuilder getMetadataBuilder() {
MetadataBuilder b = new MetadataBuilderImpl(this);
applyCustomTypes(b);
return b;
}
#Override
#Deprecated
public MetadataBuilder getMetadataBuilder(StandardServiceRegistry serviceRegistry) {
MetadataBuilder b = new MetadataBuilderImpl(this, serviceRegistry);
applyCustomTypes(b);
return b;
}
private void applyCustomTypes(MetadataBuilder b) {
b.applyBasicType(new CustomType());
...
}
}
Next, in Spring #Configuration class we set an instance of our CustomTypeIncludedMetadataSources into Spring LocalSessionFactoryBean:
#Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setMetadataSources(new CustomTypeIncludedMetadataSources());
sessionFactory.setDataSource(dataSource);
...
return sessionFactory;
}

How do I configure multiple JPA data sources using Spring #Configuration class?

I am trying to configure multiple JPA entity/transaction managers within the same application context using Spring's #Configuration class.
When the context loads, Spring is having difficulties auto-wiring the beans because they implement the same interfaces.
Unfortunately, I'm using legacy code so I can't auto-wire the beans directly and use the #Qualifier annotations, which is why I'm trying to do it using the configuration class.
Within a #Bean declaration, is there any way to qualify which bean should be injected? I thought that using a direct method call would be enough, but it typically results in errors such as
NoUniqueBeanDefinitionException: No qualifying bean of type
[javax.sql.DataSource] is defined: expected single matching bean but
found 4
Here's an example of what I'm trying to do below:
#Configuration
public class ApplicationConfig {
#Bean(name = "transactionManager1")
public PlatformTransactionManager transactionManager1() {
return new JpaTransactionManager(entityManagerFactory1());
}
#Bean(name = "entityManagerFactory1")
public EntityManagerFactory entityManagerFactory1() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource1());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test1")
public JndiObjectFactoryBean jndiObjectFactoryBean1() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource1")
public DataSource dataSource1() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean1().getJndiName());
}
#Bean(name = "transactionManager2")
public PlatformTransactionManager transactionManager2() {
return new JpaTransactionManager(entityManagerFactory2());
}
#Bean(name = "entityManagerFactory2")
public EntityManagerFactory entityManagerFactory2() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource2());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test2")
public JndiObjectFactoryBean jndiObjectFactoryBean2() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource2")
public DataSource dataSource2() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean2().getJndiName());
}
I suppose I could try to inject the beans directly via the Spring context's getBean() method, but is there a cleaner way of doing this?
I'm not too familiar with the #Primary annotation, but based on what I've read I don't know how spring would autowire the secondary data source in this case since it looks like it would always pick the beans with #Primary first.
If you cannot change the injection sites to add qualifiers, then you're going to have to create a delegating DataSource based on some logic (which you haven't detailed in the question).
Something like this.
#Primary #Bean
public DelegatingDataSource delegatingDataSource(List<DataSource> sources) {
return new DelegatingDataSource() {
#Override
public DataSource getTargetDataSource() {
// decide which dataSource to delegate to
return sources.get(0);
}
}
}
I've used DelegatingDataSource, but that may not be able to provide what you need. You may need to get more advanced with some kind of interceptor/aspect to get details of the caller on which to base the DataSource selection.
However it's implemented, you need to specify a #Primary bean and use it as a proxy.

Using I18n messages while validating forms with JSR-303

My app is fully-configured spring-boot app with thymeleaf templates engine. I18n configured as well so I can use it within my templates. Here is the config I use:
spring.messages.basename=i18n/messages
While manual validating fields I18n also work fine:
BindingResult result;
result.rejectValue("field", "some.i18n.code");
But once I want to implement some custom ConstraintValidator objects and use message field - no I18n involved, I receive plain codes as a response instead of a message. I.e.
{some.i18n.code}
I tried this solution - no result.
This on as well - same result.
What am I missing?
I guess I found the solution, maybe it will be helpful to others. All you have to do is to add the following definitions into your WebMvcConfigurerAdapter configuration implementation:
#Autowired
private MessageSource messageSource;
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setValidationMessageSource(messageSource);
return validatorFactoryBean;
}
#Override
public Validator getValidator() {
return validator();
}
An alternative solution is just declare this bean in any of your #Configuration classes:
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource);
return bean;
}
Due to declaring this, now my custom error codes from my custom validators are being searched for in my messages.properties (that I also have in a i18n subdirectory by declaring spring.messages.basename=i18n/messages).
Example validator code:
public class ContestValidator implements ConstraintValidator<ValidContest, CreateContestParameters> {
#Override
public void initialize(ValidContest constraintAnnotation) {
}
#Override
public boolean isValid(CreateContestParameters contestParameters, ConstraintValidatorContext context) {
boolean result = true;
if (!endDateIsEqualOrAfterStartDate(contestParameters)) {
context.buildConstraintViolationWithTemplate("{Contest.endDate.invalid}")
.addPropertyNode("endDate").addConstraintViolation();
result = false;
}
if (!registrationDeadlineIsBeforeStartDate(contestParameters)) {
context.buildConstraintViolationWithTemplate("{Contest.registrationDeadline.invalid}")
.addPropertyNode("registrationDeadline").addConstraintViolation();
}
return result;
}
}

Spring Autowiring in Validator for Dropwizard

I'm using the Validator API to validate my Dropwizard resources.
At this point I need to create a custom ConstraintValidator -- but my validator must be able to connect to a database.
In order to handle this, I am using #Autowire inside my custom ConstraintValidator to provide my configured beans (the database).
public class CustomValidator implements ConstraintValidator<CustomAnnotation, String> {
#Autowired private ApplicationContext applicationContext;
#Override
public void initialize(final CustomAnnotation customAnnotation) {
}
#Override
public boolean isValid(final Optional<String> value, final ConstraintValidatorContext context) {
applicationContext.getApplicationName();
return true;
}
}
In order to get autowiring to work with the validator, I believe I have to use 'LocalValidatorFactoryBean'.
I've configured mine as such:-
final LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setApplicationContext(applicationContext); // preconfigured with my beans
factory.afterPropertiesSet(); // seems to perform the setup of the ConstraintValidatorFactory with applicationContext.getAutowireCapableBeanFactory()
Dropwizard comes with a validator pre-configured in the Environment object.
So, to ensure that Dropwizard uses the Spring configured validator with injection, I've set the environment with the LocalValidatorFactoryBean like so:-
environment.setValidator(factory);
However, rather frustratingly my #Autowired private ApplicationContext applicationContext from my CustomValidator still appears to be null.
Does anyone know where I'm going wrong here or if there is an easier way?
Edit A bit more info on how it was done.
Set this up in my application:-
final ValidatorFactory constraintValidatorFactory = // custom validator factory
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
jerseyEnvironment.register(new ValidationConfigurationContextResolver(constraintValidatorFactory, factory));
Then used this class to replace the desired validator:-
public class ValidationConfigurationContextResolver implements ContextResolver<ValidationConfig> {
private final ConstraintValidatorFactory constraintValidatorFactory;
private final ValidatorFactory factory;
public ValidationConfigurationContextResolver(final ConstraintValidatorFactory constraintValidatorFactory,final ValidatorFactory factory) {
this.constraintValidatorFactory = constraintValidatorFactory;
this.factory = factory;
}
#Override
public ValidationConfig getContext(final Class<?> type) {
final ValidationConfig config = new ValidationConfig();
config.messageInterpolator(factory.getMessageInterpolator());
config.constraintValidatorFactory(constraintValidatorFactory); // custom constraint validator factory
config.parameterNameProvider(factory.getParameterNameProvider());
config.traversableResolver(factory.getTraversableResolver());
return config;
}
}

Use of multiple DataSources in Spring Batch

I am trying to configure a couple of datasources within Spring Batch. On startup, Spring Batch is throwing the following exception:
To use the default BatchConfigurer the context must contain no more thanone DataSource, found 2
Snippet from Batch Configuration
#Configuration
#EnableBatchProcessing
public class BatchJobConfiguration {
#Primary
#Bean(name = "baseDatasource")
public DataSource dataSource() {
// first datasource definition here
}
#Bean(name = "secondaryDataSource")
public DataSource dataSource2() {
// second datasource definition here
}
...
}
Not sure why I am seeing this exception, because I have seen some xml based configuration for Spring batch that declare multiple datasources. I am using Spring Batch core version 3.0.1.RELEASE with Spring Boot version 1.1.5.RELEASE. Any help would be greatly appreciated.
You must provide your own BatchConfigurer. Spring does not want to make that decision for you
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Bean
BatchConfigurer configurer(#Qualifier("batchDataSource") DataSource dataSource){
return new DefaultBatchConfigurer(dataSource);
}
...
AbstractBatchConfiguration tries to lookup BatchConfigurer in container first, if it is not found then tries to create it itself - this is where IllegalStateException is thrown where there is more than one DataSource bean in container.
The approach to solving the problem is to prevent from creation the DefaultBatchConfigurer bean in AbstractBatchConfiguration.
To do it we hint to create DefaultBatchConfigurer by Spring container using #Component annotation:
The configuration class where #EnableBatchProcessing is placed we can annotate with #ComponentScan that scan the package that contains the empty class that is derived from DefaultBatchConfigurer:
package batch_config;
...
#EnableBatchProcessing
#ComponentScan(basePackageClasses = MyBatchConfigurer.class)
public class MyBatchConfig {
...
}
the full code of that empty derived class is here:
package batch_config.components;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
import org.springframework.stereotype.Component;
#Component
public class MyBatchConfigurer extends DefaultBatchConfigurer {
}
In this configuration the #Primary annotation works for DataSource bean as in the example below:
#Configuration
public class BatchTestDatabaseConfig {
#Bean
#Primary
public DataSource dataSource()
{
return .........;
}
}
This works for the Spring Batch version 3.0.3.RELEASE
The simplest solution to make #Primary annotation on DataSource work might be just adding #ComponentScan(basePackageClasses = DefaultBatchConfigurer.class) along with #EnableBatchProcessing annotation:
#Configuration
#EnableBatchProcessing
#ComponentScan(basePackageClasses = DefaultBatchConfigurer.class)
public class MyBatchConfig {
I would like to provide a solution here, which is very similar to the one answered by #vanarchi, but I managed to put all the necessary configurations into one class.
For the sake of completeness, the solution here assumes that primary datasource is hsql.
#Configuration
#EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer {
#Bean
#Primary
public DataSource batchDataSource() {
// no need shutdown, EmbeddedDatabaseFactoryBean will take care of this
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase embeddedDatabase = builder
.addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql")
.addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql")
.setType(EmbeddedDatabaseType.HSQL) //.H2 or .DERBY
.build();
return embeddedDatabase;
}
#Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(batchDataSource());
factory.setTransactionManager(transactionManager());
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
private ResourcelessTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
//NOTE: the code below is just to provide developer an easy way to access the in-momery hsql datasource, as we configured it to the primary datasource to store batch job related data. Default username : sa, password : ''
#PostConstruct
public void getDbManager(){
DatabaseManagerSwing.main(
new String[] { "--url", "jdbc:hsqldb:mem:testdb", "--user", "sa", "--password", ""});
}
}
THREE key points in this solution:
This class is annotated with #EnableBatchProcessing and #Configuration, as well as extended from DefaultBatchConfigurer. By doing this, we instruct spring-batch to use our customized batch configurer when AbstractBatchConfiguration tries to lookup BatchConfigurer;
Annotate batchDataSource bean as #Primary, which instruct spring-batch to use this datasource as its datasource of storing the 9 job related tables.
Override protected JobRepository createJobRepository() throws Exception method, which makes the jobRepository bean to use the primary datasource, as well as use a different transactionManager instance from the other datasource(s).
The simplest solution is to extend the DefaultBatchConfigurer and autowire your datasource via a qualifier:
#Component
public class MyBatchConfigurer extends DefaultBatchConfigurer {
/**
* Initialize the BatchConfigurer to use the datasource of your choosing
* #param firstDataSource
*/
#Autowired
public MyBatchConfigurer(#Qualifier("firstDataSource") DataSource firstDataSource) {
super(firstDataSource);
}
}
Side Note (as this also deals with the use of multiple data sources): If you use autoconfig to run data initialization scripts, you may notice that it's not initializing on the datasource you'd expect. For that issue, take a look at this: https://github.com/spring-projects/spring-boot/issues/9528
You can define below beans and make sure you application.properties file has entries needed for
#Configuration
#PropertySource("classpath:application.properties")
public class DataSourceConfig {
#Primary
#Bean(name = "abcDataSource")
#ConfigurationProperties(prefix = "abc.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
#Bean(name = "xyzDataSource")
#ConfigurationProperties(prefix = "xyz.datasource")
public DataSource xyzDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
application.properties
abc.datasource.jdbc-url=XXXXX
abc.datasource.username=XXXXX
abc.datasource.password=xxxxx
abc.datasource.driver-class-name=org.postgresql.Driver
...........
...........
...........
...........
Here you can refer: Spring Boot Configure and Use Two DataSources
First, create a custom BatchConfigurer
#Configuration
#Component
public class TwoDataSourcesBatchConfigurer implements BatchConfigurer {
#Autowired
#Qualifier("dataSource1")
DataSource dataSource;
#Override
public JobExplorer getJobExplorer() throws Exception {
...
}
#Override
public JobLauncher getJobLauncher() throws Exception {
...
}
#Override
public JobRepository getJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
// use the autowired data source
factory.setDataSource(dataSource);
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return factory.getObject();
}
#Override
public PlatformTransactionManager getTransactionManager() throws Exception {
...
}
}
Then,
#Configuration
#EnableBatchProcessing
#ComponentScan("package")
public class JobConfig {
// define job, step, ...
}

Categories