Schema Based - Multitenant - Spring Boot - java

I am trying to create minimalistic Schema Based - Multitenant - Spring Boot but I am getting following error. Application can be downloaded from GitHub https://github.com/ivoronline/spring_boot_db_multitenant
Error
Unable to instantiate specified multi-tenant connection provider [com.ivoronline.spring_boot_db_multitenant.config.TenantConnectionProvider]
application.properties
#spring.jpa.properties.hibernate.multiTenancy = SCHEMA
spring.jpa.properties.hibernate.multiTenancy = SCHEMA
spring.jpa.properties.hibernate.multi_tenant_connection_provider = com.ivoronline.spring_boot_db_multitenant.config.TenantConnectionProvider
spring.jpa.properties.hibernate.tenant_identifier_resolver = com.ivoronline.spring_boot_db_multitenant.config.TenantIdentifierResolver
# JPA / HIBERNATE
spring.jpa.hibernate.ddl-auto = create
DataSourceConfig.java
#Configuration
public class DataSourceConfig {
#Bean
public DataSource dataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url("jdbc:postgresql://localhost:5432/multitenant");
dataSourceBuilder.username("postgres");
dataSourceBuilder.password("letmein");
dataSourceBuilder.driverClassName("org.postgresql.Driver");
return dataSourceBuilder.build();
}
}
TenantIdentifierResolver.java
#Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
static final String DEFAULT_TENANT = "public";
#Override
public String resolveCurrentTenantIdentifier() {
return "public";
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
TenantConnectionProvider.java
#Component
public class TenantConnectionProvider implements MultiTenantConnectionProvider {
private DataSource datasource;
public TenantConnectionProvider(DataSource dataSource) {
this.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();
//connection.createStatement().execute(String.format("SET SCHEMA \"%s\";", tenantIdentifier));
connection.createStatement().execute("SET Schema 'public'");
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
//connection.createStatement().execute(String.format("SET SCHEMA \"%s\";", TenantIdentifierResolver.DEFAULT_TENANT));
connection.createStatement().execute("SET Schema 'public'");
releaseAnyConnection(connection);
}
#Override
public boolean supportsAggressiveRelease() {
return false;
}
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
#Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
}

Hibernate expects that property hibernate.multi_tenant_connection_provider defines either already initialized object or class name which Hibernate can instantiate. In your case TenantConnectionProvider class has no default constructor, thus Hibernate fails (moreover, you expect that TenantConnectionProvider is a spring component). There are typically two options to configure such Hibernate SPIs:
Hibernate way - interact with spring context using ManagedBeanRegistry
public class TenantConnectionProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService {
private ServiceRegistryImplementor serviceRegistry;
// default constructor
public TenantConnectionProvider() {
super();
}
#Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
protected DataSource getDataSource() {
return serviceRegistry.getService(ManagedBeanRegistry.class)
.getBean(DataSource.class)
.getBeanInstance();
}
...
}
spring-boot way - configure JPA properties in Java code:
#Bean
public HibernatePropertiesCustomizer tenantConnectionProviderCustomizer(TenantConnectionProvider tenantConnectionProvider) {
return hibernateProperties -> {
hibernateProperties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, tenantConnectionProvider);
};
}

Related

Redis query with Latest date and unique id

We are using Redis connection with JedisConnectionFactory and using CrudRepository for querying data
#Component
#Configuration
public class RedisConfig {
#Bean
public RedisTemplate<String, Object> redisTemplate(RedisProperties redisProperties) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory(redisProperties));
return template;
}
private JedisConnectionFactory jedisConnectionFactory(RedisProperties redisProperties) {
RedisStandaloneConfiguration redisStandaloneConfiguration =
new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort());
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
}
My Redis Object
#RedisHash("Resource")
public class ResourceDto {
#Id private String resourceId;
#Indexed private Date lastUpdatedDate;
private String resource;
}
I want to fetch all unique ResourceDto by resourceId with Latest lastUpdatedDate.
I have tried with few things
Using findByDistinctResourceId() but it gives error
Is there any alternative?
Dont know how to query for this kind of result using RedisTemplate
public static void main(String[] args) {
ApplicationContext ctx =
SpringApplication.run(Application.class, args);
RedisTemplate redisTemplate=ctx.getBean(RedisTemplate.class);
RedisScript redisScript=new RedisScript() {
#Override
public String getSha1() {
return null;
}
#Override
public Class getResultType() {
return ResourceDto.class;
}
#Override
public String getScriptAsString() {
return "SORT Resource BY nosort";
}
};
redisTemplate.execute(redisScript,null,null);
}
RedisTemplate is configured properly but dont know how to query for all unique ResourceDto by resourceId with Latest lastUpdatedDate

DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).commit(); Not working i.e, unable to commit

I am new to JDBC template and am trying to use prepared statement for inserting data into database using auto commit mode off for achieving high performance but at the end i'm not able to commit the transaction. Please suggest some correct approach or reference that might solve my problem.
Thanks in advance...
SpringjdbcApplication.java
#SpringBootApplication
public class SpringjdbcApplication
{
public static void main(String[] args)
{
ApplicationContext context = SpringApplication.run(SpringjdbcApplication.class, args);
SampleService service = context.getBean(SampleService.class);
List<Batch> batchList = new ArrayList<>();
batchList.add(new Batch("A","B"));
batchList.add(new Batch("B","B"));
batchList.add(new Batch("C","B"));
batchList.add(new Batch("D","B"));
batchList.add(new Batch("E","B"));
System.err.println("The number of rows inserted = "+service.singleInsert(batchList));
System.err.println("The count of batch class is = "+service.getCount());
}
}
SampleConfiguration.java
#Configuration
public class SampleConfiguration
{
#Bean
public DataSource mysqlDataSource()
{
HikariConfig config= new HikariConfig();
config.setDriverClassName("ClassName");
config.setJdbcUrl("URL");
config.setUsername("User");
config.setPassword("Password");
config.setMinimumIdle(600);
config.setMaximumPoolSize(30);
config.setConnectionTimeout(251);
config.setMaxLifetime(250);
config.setAutoCommit(false);
return new HikariDataSource(config);
}
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource)
{
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
}
Batch.java
#Entity
public class Batch implements Serializable
{
private static final long serialVersionUID = -5687736664713755991L;
#Id
#Column(name="field1")
private String field1;
#Column(name="field2")
private String field2;
....... getter, setter and constructor
}
SampleService.java
#Service
public interface SampleService
{
public int singleInsert(List<Batch> batchList);
}
SampleServiceImpl.java
#Service
public class SampleServiceImpl implements SampleService
{
#Autowired
JdbcTemplate jdbcTemplate;
#Override
public int singleInsert(List<Batch> batchList)
{
for(Batch i:batchList)
{
jdbcTemplate.update("insert into batch values(?,?)",i.getField1(),i.getField2());
}
try
{
DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).commit();
}
catch(Exception e)
{
e.printStackTrace();
}
return 1;
}
}

Spring database transaction not committing data in Db

I have created a project with spring boot. I have hikariConfig to create the data source for connection pooling with the autocommmit property set as false. Doing batch insert with jdbcTemplate running inside method annotated with #Transaction for DataSourceTransactionManager. I am unable to see the data getting inserted in Db after the program execution. If I make the autocommit true in hikariconfig it works fine.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#Component
#EnableTransactionManagement
public class DataSourceConfig {
#Bean (name = "dateSourceForSqlServer")
public DataSource dataSourceForSqlServer () {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setConnectionTimeout(10000L);
hikariConfig.setIdleTimeout(10000L);
hikariConfig.setMinimumIdle(1);
hikariConfig.setMaximumPoolSize(1);
hikariConfig.setMaxLifetime(600000L);
hikariConfig.setConnectionTestQuery("select 1");
hikariConfig.setValidationTimeout(4000L);
hikariConfig.setJdbcUrl("jdbc:sqlserver://localhost:1433;database=invt_mgmt");
hikariConfig.setUsername("sa");
hikariConfig.setPassword("sql_server_pass_123");
hikariConfig.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
hikariConfig.setAutoCommit(false);
return new HikariDataSource(hikariConfig);
}
#Bean (name = "jdbcTemplateForSqlServer")
public JdbcTemplate jdbcTemplateForSqlServer () {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSourceForSqlServer());
return jdbcTemplate;
}
#Primary
#Bean(name = "invtMgmtTxMangerForSqlServer")
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSourceForSqlServer());
return manager;
}
}
#Component
public class startBean {
#Autowired
private Business Business;
#PostConstruct
public void startApp() throws SQLException {
Business.insertContainerHierarchy();
Business.insertContainerHierarchy();
}
}
#Component
class public Business {
#Autowired
#Qualifier("jdbcTemplateForSqlServer")
private JdbcTemplate jdbcTemplateForSqlServer;
String insertIntStudent = "INSERT INTO student (id, name) Values(?, ?)";
#Transactional(value = "invtMgmtTxMangerForSqlServer")
public void insertContainerHierarchy () throws SQLException {
System.out.println(TransactionSynchronizationManager.isActualTransactionActive());
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());
Date start = new Date();
for (int i = 0; i < 500; i++) {
System.out.println(i);
jdbcTemplateForSqlServer.batchUpdate(insertIntStudent, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setInt(1, i);
ps.setString(2, String.valueOf(i));
}
#Override
public int getBatchSize() {
return 1000;
}
});
}
System.out.println(new Date().getTime() - start.getTime());
}
}
I have used TransactionSynchronizationManager.isActualTransactionActive() which is returing true when the method is executed.
Q1. Why the data is not getting inserted, Transaction is supposed to autocommit once the method is executed?
Q2. In case spring transaction is getting used will database connection autocommit value make any difference?
Q3. How currently I am able to insert with autocommit set to true?
You are trying to invoke a transaction-wrapped proxy from within the #PostConstruct method. For that bean, all the initialization may be complete but not necessarily for the rest of the context. Not all proxies may be set at that point.
I would suggest implementing the ApplicationListener<ContextRefreshedEvent> interface in order to trigger any data creation inside that class. This will ensure it will be called after the entire context has been set-up:
#Component
public class OnContextInitialized implements
ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Business Business;
#Override public void onApplicationEvent(ContextRefreshedEvent event) {
Business.insertContainerHierarchy();
Business.insertContainerHierarchy();
}
}

How to manage transactions in spring boot

I have a spring boot application and use camel with it, I read a file and then I try to inserted on my DB, everything is working good the only problem is that I try to use #transactional or transactionTemplate to make a rollback when an error occur but it doesn't make the rollback,
With the #transactional I add to my SpringBootApplication the #EnableTransactionManagement(proxyTargetClass=true) and in my class I add the #Transactional(rollbackFor = Exception.class)
These are my classes:
#SpringBootApplication
#EnableDiscoveryClient
#EnableTransactionManagement(proxyTargetClass=true)
public class MsArchivo510Application {
public static void main(String[] args) {
SpringApplication.run(MsArchivo510Application.class, args);
}
}
#Service
public class ArchivoBS implements Processor{
#Transactional(rollbackFor = Exception.class)
#Override
public void process(Exchange exchange) throws Exception {
//Here I execute stored procedure and one of them fail
}
}
With the transactioTemplate my class end up like this:
#Service
public class ArchivoBS implements Processor{
#Override
public void process(Exchange exchange) throws Exception {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
//Here I execute stored procedure and one of them fail
} catch (Exception e) {
e.printStackTrace();
status.setRollbackOnly();
}
}
});
}
}
Am I missing something?, Can someone help me with this issue?,
Thanks in advance
You're in a camel context and Spring-boot may have difficulties to work properly.
You could try to make your transaction operation in a spring service and inject it in you processor then add #Transaction on your service method and call it from your processor.
At the end I noticed that I need to specify to my data source the DataSourceTransactionManager, I have a class with the annotation #Configuration and there I can create multiples data source, so my class were like this:
#Configuration
public class Configuracion {
#Bean(name = "mysqlNocturno")
#ConfigurationProperties(prefix = "spring.nocturno")
public DataSource mysqlDataSourceNocturno() {
return DataSourceBuilder.create().build();
}
#Bean(name = "jdbcTemplateNocturno")
public JdbcTemplate jdbcTemplateNocturno(#Qualifier("mysqlNocturno") DataSource dsMySQL) {
return new JdbcTemplate(dsMySQL);
}
#Bean(name = "mysqlProduccion")
#Primary
#ConfigurationProperties(prefix = "spring.produccion")
public DataSource mysqlDataSourceProduccion() {
return DataSourceBuilder.create().build();
}
#Bean(name = "jdbcTemplateProduccion")
public JdbcTemplate jdbcTemplateProduccion(#Qualifier("mysqlProduccion") DataSource dsMySQL) {
return new JdbcTemplate(dsMySQL);
}
}
The documentation mention that the annotation #EnableTransactionManagement need to be added on my SpringBootApplication class but that is not necessary, it need to be added on my configuration class, so my class end up like this:
#Configuration
#EnableTransactionManagement
public class Configuracion {
#Bean(name = "mysqlNocturno")
#ConfigurationProperties(prefix = "spring.nocturno")
public DataSource mysqlDataSourceNocturno() {
return DataSourceBuilder.create().build();
}
#Bean(name = "jdbcTemplateNocturno")
public JdbcTemplate jdbcTemplateNocturno(#Qualifier("mysqlNocturno") DataSource dsMySQL) {
return new JdbcTemplate(dsMySQL);
}
#Bean(name = "transactionManagerNocturno")
public PlatformTransactionManager transactionManagerNocturno() {
return new DataSourceTransactionManager(mysqlDataSourceNocturno());
}
#Bean(name = "mysqlProduccion")
#Primary
#ConfigurationProperties(prefix = "spring.produccion")
public DataSource mysqlDataSourceProduccion() {
return DataSourceBuilder.create().build();
}
#Bean(name = "jdbcTemplateProduccion")
public JdbcTemplate jdbcTemplateProduccion(#Qualifier("mysqlProduccion") DataSource dsMySQL) {
return new JdbcTemplate(dsMySQL);
}
#Bean(name = "transactionManagerProduccion")
public PlatformTransactionManager transactionManagerProduccion() {
return new DataSourceTransactionManager(mysqlDataSourceProduccion());
}
}
With this configuration I only need to add the #transactional annotation to my class like #Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
#Override
public void altaArchivo(Mensaje objMensaje, ArchivoCarnet objArchivoCarnet, ArchivoCarnetTrailer objArchivoCarnetTrailer, List<ArchivoCarnetDetalle> lstArchivoCarnetDetalle) {
if (objMensaje.getStrCodigo().equals(ArchivoErrorEnum.OPERACION_EXITOSA.getStrCodigo())) {
archivoDAO.altaArchivoCarnet(objArchivoCarnet);
archivoDAO.altaArchivoCarnetTrailer(objArchivoCarnetTrailer);
archivoDAO.altaArchivoCarnetDetalle(lstArchivoCarnetDetalle);
} else {
archivoDAO.altaBitacoraArchivo510(new BitacoraArchivo510(objMensaje, objArchivoCarnet.getStrNombre()));
}
}
Hope this help someone else :)

how to auto-wire HibernateBundle with guice on dropwizard?

Im trying to configure hibernatebundle with guice/dropwizard and need help.
Im using hubspot / dropwizard-guice / 0.7.0 3rd party library in addition to dropwizard lib.
The code below obviously wont work and need help on figuring it out. How do I rewrite this so that hibernatebundle and ultimately, session factory, be auto injected to whatever bean that needs it.
MyApplication.java
public class MyApplication extends Application<MyAppConfiguration> {
private final HibernateBundle<MyAppConfiguration> hibernateBundle = new HibernateBundle<MyAppConfiguration>(MyModel.class) {
#Override
public DataSourceFactory getDataSourceFactory(MyAppConfiguration configuration) {
return configuration.getDataSourceFactory();
}
};
#Override
public void initialize(Bootstrap<MyAppConfiguration> bootstrap) {
bootstrap.addBundle(hibernateBundle); // ???
bootstrap.addBundle(
GuiceBundle.<MyAppConfiguration>newBuilder()
.addModule(new MyAppModule())
.enableAutoConfig(getClass().getPackage().getName())
.setConfigClass(MyAppConfiguration.class)
.build()
);
}
}
MyAppModule.java
public class MyAppModule extends AbstractModule {
#Provides
public SessionFactory provideSessionFactory(MyAppConfiguration configuration) {
// really wrong as it creates new instance everytime.
return configuration.getHibernateBundle().getSessionFactory(); // ???
}
}
MyAppConfiguration.java
public class MyAppConfiguration extends Configuration {
#Valid
#NotNull
private DataSourceFactory database = new DataSourceFactory();
#JsonProperty("database")
public DataSourceFactory getDataSourceFactory() {
return database;
}
#JsonProperty("database")
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
this.database = dataSourceFactory;
}
// ???
public HibernateBundle<MyAppConfiguration> getHibernateBundle() {
return new HibernateBundle<MyAppConfiguration>(MyModel.class) {
#Override
public DataSourceFactory getDataSourceFactory(MyAppConfiguration configuration) {
return database;
}
};
}
}
Here is how I end up doing. I never got an answer from here or mailing list so I would consider this hackish and probably not the proper way to do it but it works for me.
In my module (that extends abstractmodule) :
private final HibernateBundle<MyConfiguration> hibernateBundle =
new HibernateBundle<MyConfiguration>(MyModel.class) {
#Override
public DataSourceFactory getDataSourceFactory(MyConfiguration configuration) {
return configuration.getDataSourceFactory();
}
};
#Provides
public SessionFactory provideSessionFactory(MyConfiguration configuration,
Environment environment) {
SessionFactory sf = hibernateBundle.getSessionFactory();
if (sf == null) {
try {
hibernateBundle.run(configuration, environment);
} catch (Exception e) {
logger.error("Unable to run hibernatebundle");
}
}
return hibernateBundle.getSessionFactory();
}
revised:
public SessionFactory provideSessionFactory(MyConfiguration configuration,
Environment environment) {
SessionFactory sf = hibernateBundle.getSessionFactory();
if (sf == null) {
try {
hibernateBundle.run(configuration, environment);
return hibernateBundle.getSessionFactory();
} catch (Exception e) {
logger.error("Unable to run hibernatebundle");
}
} else {
return sf;
}
}
I thought the explicit run(configuration, environment) call (in the answer provided by #StephenNYC) was a bit weird so a digged a little deeper. I found out that AutoConfig in dropwizard-guice wasn't setting up ConfiguredBundle's correctly (HibernateBundle is such a type).
As of https://github.com/HubSpot/dropwizard-guice/pull/35 the code can now look like this instead:
#Singleton
public class MyHibernateBundle extends HibernateBundle<NoxboxConfiguration> implements ConfiguredBundle<MyConfiguration>
{
public MyHibernateBundle()
{
super(myDbEntities(), new SessionFactoryFactory());
}
private static ImmutableList<Class<?>> myDbEntities()
{
Reflections reflections = new Reflections("com.acme");
ImmutableList<Class<?>> entities = ImmutableList.copyOf(reflections.getTypesAnnotatedWith(Entity.class));
return entities;
}
#Override
public DataSourceFactory getDataSourceFactory(NoxboxConfiguration configuration)
{
return configuration.getMyDb();
}
}
#Provides
public SessionFactory sessionFactory(MyHibernateBundle hibernate)
{
return checkNotNull(hibernate.getSessionFactory());
}
The magic behind this is that MyHibernateBundle implements ConfiguredBundle which dropwizard-guice now automatically picks up and instantiates.
Here is the way I solved it :
Put the Hibernate bundle in the guice module and pass the bootstap object as argument of guice module constructor so the hibernate bundle can be added to it.
The configuration can remain exactly as you would use a hibernate-bundle without guice.
I got this working with dropwizard-hibernate v0.7.1 and dropwizard-guice v0.7.0.3
MyAppModule.java :
public class MyAppModule extends AbstractModule {
private final HibernateBundle<MyAppConfiguration> hibernateBundle = new HibernateBundle<MyAppConfiguration>(MyModel.class) {
#Override
public DataSourceFactory getDataSourceFactory(MyAppConfiguration configuration) {
return configuration.getDataSourceFactory();
}
};
public MyAppModule(Bootstrap<MyAppConfiguration> bootstrap) {
bootstrap.addBundle(hibernateBundle);
}
#Override
protected void configure() {
}
#Provides
public SessionFactory provideSessionFactory() {
return hibernateBundle.getSessionFactory();
}
}
MyApplication.java :
public class MyApplication extends Application<MyAppConfiguration> {
#Override
public void initialize(Bootstrap<MyAppConfiguration> bootstrap) {
bootstrap.addBundle(
GuiceBundle.<MyAppConfiguration>newBuilder()
.addModule(new MyAppModule(bootstrap))
.enableAutoConfig(getClass().getPackage().getName())
.setConfigClass(MyAppConfiguration.class)
.build()
);
}
#Override
public void run(final MyAppConfiguration configuration, final Environment environment) throws Exception {
}
}
MyAppConfiguration.java :
public class MyAppConfiguration extends Configuration {
#Valid
#NotNull
#JsonProperty("database")
private DataSourceFactory database = new DataSourceFactory();
public DataSourceFactory getDataSourceFactory() {
return database;
}
}
I have not used hibernate in dropwizard, but I have used Guice and you really only need to worry about MyAppModule. That's where the magic will happen:
public class MyAppModule extends AbstractModule {
#Singleton
#Provides
public SessionFactory provideSessionFactory(MyAppConfiguration configuration) {
HibernateBundle<MyAppConfiguration> hibernate = new HibernateBundle<ExampleConfiguration>(MyModel.class) {
#Override
public DataSourceFactory getDataSourceFactory(MyAppConfiguration configuration) {
return configuration.getDataSourceFactory();
}
}
return hibernate.getSessionFactory();
}
}
(see here for multiple Classes)
MyAppConfiguration.java and MyApplication.java should not have any of the hibernate bundle references in. You should then be able to #Inject a SessionFactory where ever you need it.

Categories

Resources