I am trying to use both Cassandra and MySQL in my project. Some data will be saved into Cassandra and some to Mysql. I had been using mySql for last 1 yr in the same project and now since I'm Expanding it, I want to add Cassandra DB also.
My Cassandra Configuration file is as follows.
#Configuration
#PropertySource(value = {"classpath:META-INF/application.properties"})
#EnableCassandraRepositories(basePackages = {"com.example.repository"})
public class CassandraConfig {
#Autowired
private Environment environment;
private static final Logger LOGGER = LoggerFactory.getLogger(CassandraConfig.class);
#Bean
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints(environment.getProperty("spring.cassandra.contactpoints"));
cluster.setPort(Integer.parseInt(environment.getProperty("spring.cassandra.port")));
return cluster;
}
#Bean
public CassandraMappingContext mappingContext() {
return new BasicCassandraMappingContext();
}
#Bean
public CassandraConverter converter() {
return new MappingCassandraConverter(mappingContext());
}
#Bean
public CassandraSessionFactoryBean session() throws Exception {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(cluster().getObject());
session.setKeyspaceName(environment.getProperty("spring.cassandra.keyspace"));
session.setConverter(converter());
session.setSchemaAction(SchemaAction.NONE);
return session;
}
#Bean
public CassandraOperations cassandraTemplate() throws Exception {
return new CassandraTemplate(session().getObject());
}
}
My Repository is
public interface NewRepository extends CassandraRepository<ID>{
}
Now I'm trying to save an entity to it using the reposiroty
repo.save(entity);
where repo is the object for NewRepository.
But it shows InvalidDataAccessApiUsageException: unknown Type.
Where am i wrong.
Thank You in advance.
Related
I have an application that has a base database (Oracle). It fetches the other tenant database connection string from a table in the base database. These tenants can be Oracle or Postgres or MSSQL.
When the application starts the dialect is set to org.hibernate.dialect.SQLServerDialect by hibernate which is of the base database. But when I try to insert data in a tenant of the MSSQL database it is throwing error while inserting data. com.microsoft.sqlserver.jdbc.SQLServerException: DEFAULT or NULL are not allowed as explicit identity values
This is because it is setting MSSQL dialect for the Oracle database.
[WARN ] 2020-01-21 09:16:22.504 [https-jsse-nio-22500-exec-5] [o.h.e.j.s.SqlExceptionHelper] -- SQL Error: 339, SQLState: S0001
[ERROR] 2020-01-21 09:16:22.504 [https-jsse-nio-22500-exec-5] [o.h.e.j.s.SqlExceptionHelper] -- DEFAULT or NULL are not allowed as explicit identity values.
[ERROR] 2020-01-21 09:16:22.535 [https-jsse-nio-22500-exec-5] [o.a.c.c.C.[.[.[.[dispatcherServlet]] -- Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement] with root cause
com.microsoft.sqlserver.jdbc.SQLServerException: DEFAULT or NULL are not allowed as explicit identity values.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:217)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1655)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:440)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:385)
at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7505)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:2445)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:191)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:166)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.executeUpdate(SQLServerPreparedStatement.java:328)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:43)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3106)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3699)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:84)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:335)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:292)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:198)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:128)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:108)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:702)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:688)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
I have a TenantIdentifierResolver which implements CurrentTenantIdentifierResolver
#Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
#Autowired
PropertyConfig propertyConfig;
#Override
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getCurrentTenant();
if (tenantId != null) {
return tenantId;
}
return propertyConfig.getDefaultTenant();
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
A component class MultiTenantConnectionProviderImpl which extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl
#Component
public class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
#Autowired
private DataSource defaultDS;
#Autowired
PropertyConfig propertyConfig;
#Autowired
TenantDataSourceService tenantDBService;
private Map<String, DataSource> map = new HashMap<>();
boolean init = false;
#PostConstruct
public void load() {
map.put(propertyConfig.getDefaultTenant(), defaultDS);
ConcurrentMap<String,DataSource> tenantList = tenantDBService.getGlobalTenantDataSource(); //gets tenant datasources from service
map.putAll(tenantList);
}
#Override
protected DataSource selectAnyDataSource() {
return map.get(propertyConfig.getDefaultTenant());
}
#Override
protected DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier) != null ? map.get(tenantIdentifier) : map.get(propertyConfig.getDefaultTenant());
}
}
And a configuration class HibernateConfig
#Configuration
public class HibernateConfig {
#Autowired
private JpaProperties jpaProperties;
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource,
MultiTenantConnectionProviderImpl multiTenantConnectionProviderImpl,
TenantIdentifierResolver currentTenantIdentifierResolverImpl
) {
Map<String, Object> jpaPropertiesMap = new HashMap<>(jpaProperties.getProperties());
jpaPropertiesMap.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
jpaPropertiesMap.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
jpaPropertiesMap.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
//jpaPropertiesMap.put(Environment.DIALECT_RESOLVERS, "com.esq.cms.CashOrderMgmtService.multitenant.CustomDialectResolver");
jpaPropertiesMap.put("hibernate.jdbc.batch_size", 500);
jpaPropertiesMap.put("hibernate.order_inserts", true);
jpaPropertiesMap.put("hibernate.order_updates", true);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.esq.cms.*");
em.setJpaVendorAdapter(this.jpaVendorAdapter());
em.setJpaPropertyMap(jpaPropertiesMap);
return em;
}
}
There are many examples to set a dialect using properties file but there they have fixed type and number of databases. In my case it can be any of the database types. I have also tried adding a custom class for hibernate resolver in but it is still not working. I might be missing something. Therefore, what should I do to enable dialect as per the database by hibernate itself. Any help will be appriciated. Thanks
Try going for the Multi-tenancy strategy as DATABASE and not SCHEMA or DISCRIMINATOR as you are dealing with different types of databases (for example : Oracle, MySQL and so on).
As per Hibernate Docs : The approaches that you can take for separating data in the multi-tenant systems :
Separate Database (MultiTenancyStrategy.DATABASE) :
Each tenant's data is kept in a physically separate database instance.
JDBC connections will point to each separate database specifically so
that connection pooling will be per-single-tenant. The connection pool
is selected based on the "tenant identifier" linked to particular user.
Separate Schema (MultiTenancyStrategy.SCHEMA) :
Each tenant's data is kept in a distinct database schema on a single
database instance.
Partitioned data (MultiTenancyStrategy.DISCRIMINATOR):
All data is kept in a single database schema only. The data for each
tenant is partitioned by the use of discriminator. A single JDBC
connection pool is used for all tenants. For every SQL statement, the
app needs to manage execution of the queries on the database based on "tenant
identifier" discriminator.
Decide which strategy you want to go for based on the requirements.
I'm providing my own sample code of multi-tenancy (with spring-boot) that I have done with two different databases that is one with MySQL and another one with Postgres. This is the working example that I'm providing.
Github Repository : Working Multitenancy Code
Note: Create tables before doing any operations in the database.
I have configured all the tenants in the properties file (application.properties) with different databases.
server.servlet.context-path=/sample
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
## Tenant 1 database ##
multitenant.datasources.tenant1.url=jdbc:postgresql://localhost:5432/tenant1
multitenant.datasources.tenant1.username=postgres
multitenant.datasources.tenant1.password=Anish#123
multitenant.datasources.tenant1.driverClassName=org.postgresql.Driver
## Tenant 2 database ##
multitenant.datasources.tenant2.url=jdbc:mysql://localhost:3306/tenant2
multitenant.datasources.tenant2.username=root
multitenant.datasources.tenant2.password=Anish#123
multitenant.datasources.tenant2.driverClassName=com.mysql.cj.jdbc.Driver
MultiTenantProperties : This class binds and validates the properties set for multiple tenants and keeping them as a map of tenant vs required database information.
#Component
#ConfigurationProperties(value = "multitenant")
public class MultiTenantProperties {
private Map<String, Map<String, String>> datasources = new LinkedHashMap<>();
public Map<String, Map<String, String>> getDatasources() {
return datasources;
}
public void setDatasources(Map<String, Map<String, String>> datasources) {
this.datasources = datasources;
}
}
ThreadLocalTenantStorage : This class keeps the name of the tenant coming from the incoming request for the current thread to perform CRUD operations.
public class ThreadLocalTenantStorage {
private static ThreadLocal<String> currentTenant = new ThreadLocal<>();
public static void setTenantName(String tenantName) {
currentTenant.set(tenantName);
}
public static String getTenantName() {
return currentTenant.get();
}
public static void clear() {
currentTenant.remove();
}
}
MultiTenantInterceptor : This class intercepts the incoming request and sets the ThreadLocalTenantStorage with current tenant for the database to be selected. After completion of the request, the tenant is removed from the ThreadLocalTenantStorage class.
public class MultiTenantInterceptor extends HandlerInterceptorAdapter {
private static final String TENANT_HEADER_NAME = "TENANT-NAME";
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String tenantName = request.getHeader(TENANT_HEADER_NAME);
ThreadLocalTenantStorage.setTenantName(tenantName);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
ThreadLocalTenantStorage.clear();
}
}
TenantIdentifierResolver : This class is responsible to return the current tenant coming from ThreadLocalTenantStorage to select the datasource.
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
private static String DEFAULT_TENANT_NAME = "tenant1";
#Override
public String resolveCurrentTenantIdentifier() {
String currentTenantName = ThreadLocalTenantStorage.getTenantName();
return (currentTenantName != null) ? currentTenantName : DEFAULT_TENANT_NAME;
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
WebConfiguration : This is configuration to register the MultiTenantInterceptor class to be used as an interceptor.
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MultiTenantInterceptor());
}
}
DataSourceMultiTenantConnectionProvider : This class selects the datasource based on the tenant name.
public class DataSourceMultiTenantConnectionProvider extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final long serialVersionUID = 1L;
#Autowired
private Map<String, DataSource> multipleDataSources;
#Override
protected DataSource selectAnyDataSource() {
return multipleDataSources.values().iterator().next();
}
#Override
protected DataSource selectDataSource(String tenantName) {
return multipleDataSources.get(tenantName);
}
}
MultiTenantJPAConfiguration : This class configures the custom beans for database transactions and register the tenant datasources for multi-tenancy.
#Configuration
#EnableJpaRepositories(basePackages = { "com.example.multitenancy.dao" }, transactionManagerRef = "multiTenantTxManager")
#EnableConfigurationProperties({ MultiTenantProperties.class, JpaProperties.class })
#EnableTransactionManagement
public class MultiTenantJPAConfiguration {
#Autowired
private JpaProperties jpaProperties;
#Autowired
private MultiTenantProperties multiTenantProperties;
#Bean
public MultiTenantConnectionProvider multiTenantConnectionProvider() {
return new DataSourceMultiTenantConnectionProvider();
}
#Bean
public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
return new TenantIdentifierResolver();
}
#Bean(name = "multipleDataSources")
public Map<String, DataSource> repositoryDataSources() {
Map<String, DataSource> datasources = new HashMap<>();
multiTenantProperties.getDatasources().forEach((key, value) -> datasources.put(key, createDataSource(value)));
return datasources;
}
private DataSource createDataSource(Map<String, String> source) {
return DataSourceBuilder.create().url(source.get("url")).driverClassName(source.get("driverClassName"))
.username(source.get("username")).password(source.get("password")).build();
}
#Bean
public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
return entityManagerFactoryBean.getObject();
}
#Bean
public PlatformTransactionManager multiTenantTxManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
MultiTenantConnectionProvider multiTenantConnectionProvider,
CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
Map<String, Object> hibernateProperties = new LinkedHashMap<>();
hibernateProperties.putAll(this.jpaProperties.getProperties());
hibernateProperties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
hibernateProperties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
hibernateProperties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPackagesToScan("com.example.multitenancy.entity");
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setJpaPropertyMap(hibernateProperties);
return entityManagerFactoryBean;
}
}
Sample Entity class for testing :
#Entity
#Table(name = "user_details", schema = "public")
public class User {
#Id
#Column(name = "id")
private Long id;
#Column(name = "full_name", length = 30)
private String name;
public User() {
super();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Sample Repository for testing :
public interface UserRepository extends JpaRepository<User, Long>{
}
Sample Controller :
#RestController
#Transactional
public class SampleController {
#Autowired
private UserRepository userRepository;
#GetMapping(value = "/{id}")
public ResponseEntity<User> getUser(#PathVariable("id") String id) {
Optional<User> user = userRepository.findById(Long.valueOf(id));
User userDemo = user.get();
return ResponseEntity.ok(userDemo);
}
#PostMapping(value = "/create/user")
public ResponseEntity<String> createUser(#RequestBody User user) {
userRepository.save(user);
return ResponseEntity.ok("User is saved");
}
}
Based on our current analysis for Spring/JPA Multitenancy implementation works with connecting to multiple database types (MSSQL, PostgGreSQL), only when you have an initial DataSource to connect at the startup. Spring/JPA/Hibernate framework support does require a Dialect setup during application startup and will throw an error if you don't set one. Our requirement was to get these connections in a lazy fashion through another service that requires a tenant context. We implemented a work around to use a lightweight empty/dummy in-memory sqlite DB that we are connecting at the startup to pass through the initial dialect and connection required. This is the path for least customization to current framework code and hopefully this will be added as a feature down the road for Multitenancy implementation.
The key method that needs an initial connect and helps to connect to multiple types of DBs as needed later is in class that
extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl and override following method:
#Override
protected DataSource selectAnyDataSource() {
//TODO This method is called more than once. So check if the data source map
// is empty. If it is then set default tenant for now.
// This is test code and needs to figure out making it work for application scenarios
if (tenantDataSources.isEmpty()) {
tenantDataSources.put("default", dataSource.getDataSource(""));
log.info("selectAnyDataSource() method call...Total tenants:" + tenantDataSources.size());
}
return this.tenantDataSources.values().iterator().next();
}
I have a spring batch application to get a file in samba server
and generate a new file in a different folder on the same server.
However,
only ItemReader is called in the flow.
What is the problem? Thanks.
BatchConfiguration:
#Configuration
#EnableBatchProcessing
public class BatchConfiguration extends BaseConfiguration {
#Bean
public ValeTrocaItemReader reader() {
return new ValeTrocaItemReader();
}
#Bean
public ValeTrocaItemProcessor processor() {
return new ValeTrocaItemProcessor();
}
#Bean
public ValeTrocaItemWriter writer() {
return new ValeTrocaItemWriter();
}
#Bean
public Job importUserJob(JobCompletionNotificationListener listener) throws Exception {
return jobBuilderFactory()
.get("importUserJob")
.incrementer(new RunIdIncrementer())
.repository(getJobRepository())
.listener(listener)
.start(this.step1())
.build();
}
#Bean
public Step step1() throws Exception {
return stepBuilderFactory()
.get("step1")
.<ValeTroca, ValeTroca>chunk(10)
.reader(this.reader())
.processor(this.processor())
.writer(this.writer())
.build();
}
}
BaseConfiguration:
public class BaseConfiguration implements BatchConfigurer {
#Bean
#Override
public PlatformTransactionManager getTransactionManager() {
return new ResourcelessTransactionManager();
}
#Bean
#Override
public SimpleJobLauncher getJobLauncher() throws Exception {
final SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(this.getJobRepository());
return simpleJobLauncher;
}
#Bean
#Override
public JobRepository getJobRepository() throws Exception {
return new MapJobRepositoryFactoryBean(this.getTransactionManager()).getObject();
}
#Bean
#Override
public JobExplorer getJobExplorer() {
MapJobRepositoryFactoryBean repositoryFactory = this.getMapJobRepositoryFactoryBean();
return new SimpleJobExplorer(repositoryFactory.getJobInstanceDao(), repositoryFactory.getJobExecutionDao(),
repositoryFactory.getStepExecutionDao(), repositoryFactory.getExecutionContextDao());
}
#Bean
public MapJobRepositoryFactoryBean getMapJobRepositoryFactoryBean() {
return new MapJobRepositoryFactoryBean(this.getTransactionManager());
}
#Bean
public JobBuilderFactory jobBuilderFactory() throws Exception {
return new JobBuilderFactory(this.getJobRepository());
}
#Bean
public StepBuilderFactory stepBuilderFactory() throws Exception {
return new StepBuilderFactory(this.getJobRepository(), this.getTransactionManager());
}
}
ValeTrocaItemReader:
#Configuration
public class ValeTrocaItemReader implements ItemReader<ValeTroca>{
#Value(value = "${url}")
private String url;
#Value(value = "${user}")
private String user;
#Value(value = "${password}")
private String password;
#Value(value = "${domain}")
private String domain;
#Value(value = "${inputDirectory}")
private String inputDirectory;
#Bean
#Override
public ValeTroca read() throws MalformedURLException, SmbException, IOException, Exception {
File tempOutputFile = getInputFile();
DefaultLineMapper<ValeTroca> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(new DelimitedLineTokenizer() {
{
setDelimiter(";");
setNames(new String[]{"id_participante", "cpf", "valor"});
}
});
lineMapper.setFieldSetMapper(
new BeanWrapperFieldSetMapper<ValeTroca>() {
{
setTargetType(ValeTroca.class);
}
});
FlatFileItemReader<ValeTroca> itemReader = new FlatFileItemReader<>();
itemReader.setLinesToSkip(1);
itemReader.setResource(new FileUrlResource(tempOutputFile.getCanonicalPath()));
itemReader.setLineMapper(lineMapper);
itemReader.open(new ExecutionContext());
tempOutputFile.deleteOnExit();
return itemReader.read();
}
Sample of ItemProcessor:
public class ValeTrocaItemProcessor implements ItemProcessor<ValeTroca, ValeTroca> {
#Override
public ValeTroca process(ValeTroca item) {
//Do anything
ValeTroca item2 = item;
System.out.println(item2.getCpf());
return item2;
}
EDIT:
- Spring boot 2.1.2.RELEASE - Spring batch 4.1.1.RELEASE
Looking at your configuration, here are a couple of notes:
BatchConfiguration looks good. That's a typical job with a single chunk-oriented step.
BaseConfiguration is actually the default configuration you get when using #EnableBatchProcessing without providing a datasource. So this class can be removed
Adding #Configuration on ValeTrocaItemReader and marking the method read() with #Bean is not correct. This means your are declaring a bean named read of type ValeTroca in your application context. Moreover, your custom reader uses a FlatFileItemReader but has no added value compared to a FlatFileItemReader. You can declare your reader as a FlatFileItemReader and configure it as needed (resource, line mapper, etc ). This will also avoid the mistake of opening the execution context in the read method, which should be done when initializaing the reader or in the ItemStream#open method if the reader implements ItemStream
Other than that, I don't see from what you shared why the processor and writer are not called.
SOLVED: The problem was that even though I'm not using any databases, the spring batch, although configured to have the JobRepository in memory, needs a database (usually H2) to save the configuration tables, jobs, etc.
In this case, the dependencies of JDBC and without H2 in pom.xml were disabled. Just added them to the project and the problem was solved!
I am working on spring boot multi tenancy application. I have configured multi datasources as shown below :
application.properties
spring.multitenancy.datasource1.url=jdbc:mysql://localhost:3306/db1
spring.multitenancy.datasource1.username=root
spring.multitenancy.datasource1.password=****
spring.multitenancy.datasource1.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.multitenancy.datasource2.url=jdbc:mysql://localhost:3306/db2
spring.multitenancy.datasource2.username=root
spring.multitenancy.datasource2.password=****
spring.multitenancy.datasource2.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.multitenancy.datasource3.url=jdbc:mysql://localhost:3306/db3
spring.multitenancy.datasource3.username=root
spring.multitenancy.datasource3.password=****
spring.multitenancy.datasource3.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
DataSourceBasedMultiTenantConnectionProviderImpl.java
#Component
public class DataSourceBasedMultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final long serialVersionUID = 8168907057647334460L;
private static final String DEFAULT_TENANT_ID = "tenant_1";
#Autowired
private DataSource dataSource1;
#Autowired
private DataSource dataSource2;
#Autowired
private DataSource dataSource3;
private Map<String, DataSource> map;
#PostConstruct
public void load() {
map = new HashMap<>();
map.put("tenant_1", dataSource1);
map.put("tenant_2", dataSource2);
map.put("tenant_3", dataSource3);
}
#Override
protected DataSource selectAnyDataSource() {
return map.get(DEFAULT_TENANT_ID);
}
#Override
protected DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier);
}
}
MultitenancyProperties.java
#ConfigurationProperties("spring.multitenancy")
public class MultitenancyProperties {
#NestedConfigurationProperty
private DataSourceProperties datasource1;
#NestedConfigurationProperty
private DataSourceProperties datasource2;
#NestedConfigurationProperty
private DataSourceProperties datasource3;
public DataSourceProperties getDatasource1() {
return datasource1;
}
public void setDatasource1(DataSourceProperties datasource1) {
this.datasource1 = datasource1;
}
public DataSourceProperties getDatasource2() {
return datasource2;
}
public void setDatasource2(DataSourceProperties datasource2) {
this.datasource2 = datasource2;
}
public DataSourceProperties getDatasource3() {
return datasource3;
}
public void setDatasource3(DataSourceProperties datasource3) {
this.datasource3 = datasource3;
}
}
MultiTenancyJpaConfiguration.java
#Configuration
#EnableConfigurationProperties(JpaProperties.class)
public class MultiTenancyJpaConfiguration {
#Autowired
private DataSource dataSource;
#Autowired
private JpaProperties jpaProperties;
#Autowired
private MultiTenantConnectionProvider multiTenantConnectionProvider;
#Autowired
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
Map<String, Object> hibernateProps = new LinkedHashMap<>();
hibernateProps.putAll(jpaProperties.getHibernateProperties(dataSource));
hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
hibernateProps.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");
return builder.dataSource(dataSource).packages(HotelEntity.class.getPackage().getName()).properties(hibernateProps).jta(false).build();
}
}
Application launcher
#SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
#EnableConfigurationProperties(MultitenancyProperties.class)
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
When I run the boot application, all tables are created in only first data source.
1) How can I create tables in all data sources on application startup?
2) How to see connections opened/closed for each of the data sources?
3) Is there a better way of configuring multi tenancy application using spring boot for better performance?
As #Alex said, you need differnt EntityManagers, TransactionManager and Datasources. Here is how I would do it
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "dataSource1EntityManagerFactory",
transactionManagerRef = "dataSource1TransactionManager",
basePackageClasses = dataSource1Repository.class)
public class DataSource1Config extends SqlConfig{// Put all common code in base class SqlConfig. If not remove it
#Bean
#Primary
public DataSource dataSource1() {
//create dataSource using MultitenancyProperties::getDataSource1
}
#Primary
#Bean(name = "dataSource1TransactionManager")
PlatformTransactionManager dataSource1TransactionManager(EntityManagerFactory dataSource1EntityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(dataSource1EntityManagerFactory);
return txManager;
}
#Primary
#Bean(name = "dataSource1EntityManagerFactory")
LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource1());
em.setPackagesToScan(dataSource1Repository.class.getPackage().getName(), dataSource1ModelClass.class.getPackage().getName());
em.setPersistenceUnitName("dataSource1Db");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
}
You can create two other classes like this. Remember to use #Primary on only one instace of datasource, transactionmanger and entitymanager(doesn't matter which one). Another word of caution, make sure Repository classes are in different packages for all three databases.
Try adding the following properties:
spring.jpa.generate-ddl=true
You need 2 different persistence factories, not one, each should produce different EntityManagers for different datasources. Also each entity mapping should be marked to been used only with one entity manager.
See full solution here:
http://www.baeldung.com/spring-data-jpa-multiple-databases
As am a beginner in writing junit test case, I have no idea to write junit to bean loader class, Can anybody suggest me how to write junit for my bean loader, Below is my piece of code:
My Bean loader is of cassandra properties:
#Configuration
#PropertySource("cassandra.properties")
#EnableCassandraRepositories(basePackages = "...repository")
public class Beanloader {
#Autowired
public Environment environment;
CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
#Bean(name = "clusterFactory")
public CassandraClusterFactoryBean getCluster() {
PoolingOptions poolingOptions = new PoolingOptions();
cluster.setContactPoints(environment.getProperty("cassandra.contactpoints"));
cluster.setPoolingOptions(poolingOptions);
cluster.setPort(Integer.parseInt(environment.getProperty("cassandra.port")));
poolingOptions.setNewConnectionThreshold(HostDistance.LOCAL, 50);
return cluster;
}
#Bean
#DependsOn("clusterFactory")
public CassandraSessionFactoryBean getSession() throws Exception {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(cluster.getObject());
session.setKeyspaceName(environment.getProperty("cassandra.keyspace"));
session.setConverter(new MappingCassandraConverter(new CassandraMappingContextAware()));
session.setSchemaAction(SchemaAction.NONE);
return session;
}
#Bean
public CassandraOperations cassandraTemplate() throws Exception {
return new CassandraTemplate(getSession().getObject());
}
}
I'm also beginner, but I think that what you search (test a config class) is an integration tests not unit test ?
I am trying to write a test for custom spring data repository. I'm also using QueryDSL.
I am new to spring-data. I use spring support for HSQL DB in testing. MySQL for dev.
Problem: I do not see updated data in tests if I use custom repository.
public interface AuctionRepository extends AuctionRepositoryCustom, CrudRepository<Auction, Long>, QueryDslPredicateExecutor<Auction> {
// needed for spring data crud
}
.
public interface AuctionRepositoryCustom {
long renameToBestName();
}
.
public class AuctionRepositoryImpl extends QueryDslRepositorySupport implements AuctionRepositoryCustom {
private static final QAuction auction = QAuction.auction;
public AuctionRepositoryImpl() {
super(Auction.class);
}
#Override
public long renameToBestName() {
return update(auction)
.set(auction.name, "BestName")
.execute();
}
}
My test
Somehow fails at last line
public class CustomAuctionRepositoryImplTest extends AbstractIntegrationTest {
#Inject
AuctionRepository auctionRepository;
#Test
public void testDoSomething() {
Auction auction = auctionRepository.findOne(26L);
assertEquals("EmptyName", auction.getName());
// test save
auction.setName("TestingSave");
auctionRepository.save(auction);
Auction saveResult = auctionRepository.findOne(26L);
assertEquals("TestingSave", saveResult.getName());
// test custom repository
long updatedRows = auctionRepository.renameToBestName();
assertTrue(updatedRows > 0);
Auction resultAuction = auctionRepository.findOne(26L);
assertEquals("BestName", resultAuction.getName()); // FAILS expected:<[BestNam]e> but was:<[TestingSav]e>
}
}
I can't figure out why data doesn't update when using custom repository. If I start application in dev mode, and call renameToBestName() through controller, everything works as expected, name changes.
Below is Test Configuration if needed
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#ActiveProfiles("test")
#ContextConfiguration(classes = {TestBeans.class, JpaConfig.class, EmbeddedDataSourceConfig.class})
#ComponentScan(basePackageClasses = IntegrationTest.class, excludeFilters = #Filter({Configuration.class}))
public abstract class AbstractIntegrationTest {
}
.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = Application.class)
class JpaConfig {
#Value("${hibernate.dialect}")
private String dialect;
#Value("${hibernate.hbm2ddl.auto}")
private String hbm2ddlAuto;
#Value("${hibernate.isShowSQLOn}")
private String isShowSQLOn;
#Autowired
private DataSource dataSource;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setPackagesToScan("auction");
entityManagerFactory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties jpaProperties = new Properties();
jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, dialect);
if ( !hbm2ddlAuto.isEmpty()) {
jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, hbm2ddlAuto);
}
jpaProperties.put(org.hibernate.cfg.Environment.SHOW_SQL, isShowSQLOn);
jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_IMPORT_FILES_SQL_EXTRACTOR, "org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor");
entityManagerFactory.setJpaProperties(jpaProperties);
return entityManagerFactory;
}
#Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
This is due to the update query issued through your code is defined to not evict the object potentially touched by the query from the EntityManager. Read more on that in this answer.