Spring boot dynamic database URL based on REST parameters - java

I am using Spring boot to develop an application, we have one database for each customer, and the main problem is to avoid creating one instance for each, connected to a single database. With that in mind, I got stuck in the following situation:
I need my application to make connections to several different databases, witch Aliases I don't know when the application starts. Those aliases will be provided via REST requests, so it's dynamic.
All other credentials are equal on the databases, the Driver, URL, username and password, I really just need to change the alias to provide the right URL for that request.
I've done it creating datasources at the momment of the request, like this:
private static void selectAll(String alias){
String sql = "select * from user";
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url("jdbc:postgresql://localhost:5432/"+alias);
dataSourceBuilder.username("username");
dataSourceBuilder.password("password");
DataSource dataSource = dataSourceBuilder.build();
try {
Connection con = dataSource.getConnection();
con.setAutoCommit(true);
con.prepareStatement(sql).executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
}
But by doing things that way, i can't use criteria and hibernate, and I have to map my objects retreived from the database by hand and write my own simple SQLs for each object/table.
The main question is, can i use Entities and Repositories (Spring data) with dynamic databases, or if I go for this I'll have to choose between those and having one instance for each customer, connected to a single database.
I've searched on forums, and the solutions are eigther based on static connections, configured inside Application.properties, or they assume that the the URL is known.
Thank you for your help, any other better aproaches are wellcome, any toughts at all.

Related

Is it possible to return Cassandra Session object in REST API?

I'm looking for possible solutions to connect to multiple Cassandra clusters. I tried some solutions presented, but couldn't make them work. So, I have decided to create a microservice to connect to the 2nd cluster. However, I'm trying to reduce the amount of duplicate code for the prepared statements. So, I was thinking, if it's possible to return the session object.
When I try to run this snippet of code, I get Http Status as 415, unsupported media type. Is this the right solution to reduce the duplicate code or what are other options ?
#RestController
#RequestMapping("/")
public class controller {
private CassandraOperations cassandraOperations;
private CqlTemplate cqlTemplate;
#PostMapping(value="/value")
public Session returnSession(String query) {
cqlTemplate = (CqlTemplate) cassandraOperations.getCqlOperations();
return cqlTemplate.getSessionFactory().getSession();
}
}
In case you haven't already seen them, these are some of the implementations I've seen:
Spring Boot 2 Cassandra multiple keyspaces or clusters
Connect to 2 cassandra clusters in spring data
https://gist.github.com/mp911de/5da6b66c546f840030ebfd3f8a3ea0e2
For the record, I haven't tried them myself so YMMV. Cheers!

Hibernate database per tenant multi-tenant connection provider is creating multiple database connections for single tenant

I want to know how hibernate's multi-tenant connection provider handles the data base connections for multiple tenants in separate database multi-tenancy approach. If I hit the API with same tenant, say 1234, then at first hit it should CONNECT to that tenant's specific database and after several hits for same tenant, will it use the same connection of database or it will open the new connection again for the same tenant?
I have used AbstractDataSourceBasedMultiTenantConnectionProviderImpl and CurrentTenantIdentifierResolver implementations in database per tenant approach
class CurrentTenantIdentifierResolver {
// Other methods + fields
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getCurrentTenant();
return tenantId;
}
}
class AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
// Other methods + fields
#Override protected DataSource selectDataSource(String tenantIdentifier) {
return MultiTenantInitService.getDataSourceMap().get(tenantIdentifier);
}
}
class MultiTenantInitService {
// Other methods + fields
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(prop.getProperty("spring.jpa.properties.hibernate.driver-class-name"));
dataSource.setUrl(prop.getProperty("spring.datasource.url"));
dataSource.setUsername(prop.getProperty("spring.datasource.username"));
dataSource.setPassword(prop.getProperty("spring.datasource.password"));
dataSourceMap.put(ApplicationConstants.DEFAULT_TENANT_ID, dataSource);
}
I expected multi tenant connection provider to connect to the database on first hit of API for single tenant for once only. It should not open the connections again and again for the same tenant. The new connection with data base should only be formed for new tenant. But, if it does open the connections then it should also manage the connection closing on its own.
I think what you want is,
spring.datasource.max-active=1
This property limits the maximum active connections to your database. So you can set this property to your DataSource and use it. which means you only have one connection in the connection pool. But there are pros and cons of this approach as if in any means the connection is corrupted, you will have to work on creating a different connection to work on the particular tenant again. So there are pretty good reasons to have a connection pool instead of having just one connection.
So the better solution would have a small connection pool as per your requirement.
If your application is constantly accessing the database its obvious that you need to have a connection pool. So I would suggest you have small connection pools per tenant.
spring.datasource.max-active=5

How can I use Hibernate/JPA to tell the DB who the user is before inserts/updates/deletes?

Summary (details below):
I'd like to make a stored proc call before any entities are saved/updated/deleted using a Spring/JPA stack.
Boring details:
We have an Oracle/JPA(Hibernate)/Spring MVC (with Spring Data repos) application that is set up to use triggers to record history of some tables into a set of history tables (one history table per table we want audited). Each of these entities has a modifiedByUser being set via a class that extends EmptyInterceptor on update or insert. When the trigger archives any insert or update, it can easily see who made the change using this column (we're interested in which application user, not database user). The problem is that for deletes, we won't get the last modified information from the SQL that is executed because it's just a plain delete from x where y.
To solve this, we'd like to execute a stored procedure to tell the database which app user is logged in before executing any operation. The audit trigger would then look at this value when a delete happens and use it to record who executed the delete.
Is there any way to intercept the begin transaction or some other way to execute SQL or a stored procedure to tell the db what user is executing the inserts/updates/deletes that are about to happen in the transaction before the rest of the operations happen?
I'm light on details about how the database side will work but can get more if necessary. The gist is that the stored proc will create a context that will hold session variables and the trigger will query that context on delete to get the user ID.
From the database end, there is some discussion on this here:
https://docs.oracle.com/cd/B19306_01/network.102/b14266/apdvprxy.htm#i1010372
Many applications use session pooling to set up a number of sessions
to be reused by multiple application users. Users authenticate
themselves to a middle-tier application, which uses a single identity
to log in to the database and maintains all the user connections. In
this model, application users are users who are authenticated to the
middle tier of an application, but who are not known to the
database.....in these situations, the application typically connects
as a single database user and all actions are taken as that user.
Because all user sessions are created as the same user, this security
model makes it very difficult to achieve data separation for each
user. These applications can use the CLIENT_IDENTIFIER attribute to
preserve the real application user identity through to the database.
From the Spring/JPA side of things see section 8.2 at the below:
http://docs.spring.io/spring-data/jdbc/docs/current/reference/html/orcl.connection.html
There are times when you want to prepare the database connection in
certain ways that aren't easily supported using standard connection
properties. One example would be to set certain session properties in
the SYS_CONTEXT like MODULE or CLIENT_IDENTIFIER. This chapter
explains how to use a ConnectionPreparer to accomplish this. The
example will set the CLIENT_IDENTIFIER.
The example given in the Spring docs uses XML config. If you are using Java config then it looks like:
#Component
#Aspect
public class ClientIdentifierConnectionPreparer implements ConnectionPreparer
{
#AfterReturning(pointcut = "execution(* *.getConnection(..))", returning = "connection")
public Connection prepare(Connection connection) throws SQLException
{
String webAppUser = //from Spring Security Context or wherever;
CallableStatement cs = connection.prepareCall(
"{ call DBMS_SESSION.SET_IDENTIFIER(?) }");
cs.setString(1, webAppUser);
cs.execute();
cs.close();
return connection;
}
}
Enable AspectJ via a Configuration class:
#Configuration
#EnableAspectJAutoProxy
public class SomeConfigurationClass
{
}
Note that while this is hidden away in a section specific to Spring's Oracle extensions it seems to me that there is nothing in section 8.2 (unlike 8.1) that is Oracle specific (other than the Statement executed) and the general approach should be feasible with any Database simply by specifying the relevant procedure call or SQL:
Postgres for example as the following so I don't see why anyone using Postgres couldn't use this approach with the below:
https://www.postgresql.org/docs/8.4/static/sql-set-role.html
Unless your stored procedure does more than what you described, the cleaner solution is to use Envers (Entity Versioning). Hibernate can automatically store the versions of an entity in a separate table and keep track of all the CRUD operations for you, and you don't have to worry about failed transactions since this will all happen within the same session.
As for keeping track who made the change, add a new colulmn (updatedBy) and just get the login ID of the user from Security Principal (e.g. Spring Security User)
Also check out #CreationTimestamp and #UpdateTimestamp.
I think what you are looking for is a TransactionalEvent:
#Service
public class TransactionalListenerService{
#Autowired
SessionFactory sessionFactory;
#TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleEntityCreationEvent(CreationEvent<Entity> creationEvent) {
// use sessionFactory to run a stored procedure
}
}
Registering a regular event listener is done via the #EventListener
annotation. If you need to bind it to the transaction use
#TransactionalEventListener. When you do so, the listener will be
bound to the commit phase of the transaction by default.
Then in your transactional services you register the event where necessary:
#Service
public class MyTransactionalService{
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Transactional
public void insertEntityMethod(Entity entity){
// insert
// Publish event after insert operation
applicationEventPublisher.publishEvent(new CreationEvent(this, entity));
// more processing
}
}
This can work also outside the boundaries of a trasaction if you have the requirement:
If no transaction is running, the listener is not invoked at all since
we can’t honor the required semantics. It is however possible to
override that behaviour by setting the fallbackExecution attribute of
the annotation to true.

Issue on opening MongoDB Connections after calling close Method

I am working on a web project using java with mongoDB as back-end database.To open a connection once and reusing the same for each service contained in the project, i am following the below URL
mongodb open connection issue .For closing the connections which are opened, i'm using the function MongoDBClass.INSTANCE.close(); during the user logout of the session in web site.But the problem is, once the user login the session again it produces the following error java.lang.IllegalStateException: state should be: open.That means the connection is not opened , MongoDBClass INSTANCE is not reinitialized so MongoClient is not reopening the connection.But after the server restarts login works perfectly for first time. How to build a new connection again after when i call close method during logout the session of user without restarting the server. I am using the following code
public enum MongoDBClass {
INSTANCE;
private static final String MONGO_DB_HOST = "hostURL";
private Mongo mongoObject;
private DB someDB;
String DB_NAME = null;
MongoClientOptions options = null;
MongoDBClass() {
options = MongoClientOptions.builder().connectionsPerHost(100)
.readPreference(ReadPreference.secondaryPreferred()).build();
mongoObject = new MongoClient(new ServerAddress(MONGO_DB_HOST, 27001),
options);
someDB = mongoObject.getDB(Nutans_Mongo.getNameOFDB());}}
public DB getSomeDB() {
return someDB;
}
public void setSomeDB(String dbName) {
someDB = mongoObject.getDB(dbName);
DB_NAME = dbName;
}
public String close() {
mongoObject.close();
return "true";
}
}
MongoClient maintains a connection pool internally so there is no need to open/close a client for each request. Also, Java enums are not meant to be used this way. Any state an enum has should be globally usable as there will only be one instance of a enum value per ClassLoader/VM. When you call close(), you're globally closing that enum's MongoClient. Since you open the connection in the constructor it never gets reopened because a another INSTANCE is never created.
There are several approaches to ensuring a singleton-like lifecycle of objects in a servlet context. Using CDI to create and inject a MongoClient in to your servlet is one way. Using a ServletContextListener and static field is another, if slightly less savory, approach.
I have the same problem. I'm using the Mongo Java driver 3.0.0.
I upgraded my database from 2.4 to 2.6. But the problem persists.
When I don't close the connection, next time it connects successfully, but in this case open connections raises quickly.

Dynamically configuring datasources

The situation is this: a table that is used in a query (name it SUMMARY) was formerly in the same server and database that I'm doing all the queries of the application (name it server1 and DB1). But recently the SUMMARY table was deleted from this database, which makes it necessary to consult other server / database combinations.
The data from the server name and the database to be used for access to the SUMMARY table are parameterized in a table for this purpose. These data depend on the database which is connected, this way: for example, if I'm in the database DB1 of server1 then parameters will be server21 and DB21, whereas if someone refers to the parameters from the DB5 from server1 parameters will be server16 and DB16.
On that side I have no problem because I have listed the SQL query of the two parameters, ready to give the name of the server and database to consult in each case. This query is necessary in order to give server name and database name with which dynamically generate the datasource to connect to.
The problem, and the topic of this entry is, whether anyone has ever had to dynamically configure the datasource to be used in hibernate.properties, since this is usually a single, fixed value and in this case should be allowed changes in order to view the SUMMARY table (using parameters retrieved by my SQL query) only in this specific case, while all other database operations must be performed by using the original connection properties.
That is: What I need is to dynamically generate the datasource based on the parameters coming from the query, so the approaches which handle this by knowing beforehand how many and what are the possible connections should be discarded because they are not viable to solve my problem.
The application specifications are:
Database engine: SQL Server 2005
Prog. Language: Java 5.0
Frameworks: Spring 2.0.4, Hibernate 3.0.5
App. Server: WAS 6.1
Thanks in advance to anyone who has that knowledge and willing to share.
You can use a ConnectionProvider in hibernate to decide how to get the connection to be used by a session. We use something like this in our application:
public Connection getConnection() throws SQLException {
DataSource ds = (DataSource) BeanFactory.getBean("dataSource" + UserInfo.getDSName());
return ds.getConnection();
}
UserInfo is a class that store stuff in a ThreadLocal. This code will chose a datasource from Spring depending on the name that was passed in the ThreadLocal. What we do is to set the name of the datasource we want to use before opening the session (the actual logic is a little bit more complicated than that, as it depends on user preferences and other stuff).
You can do something like that to choose what Database to connect to.
You can check the javadocs for the ConnectionProvider interface. Make your own implementation, set the hibernate.connection.provider_class property in your hibernate configuration file to point to your class, and you're done.
I guess, it depends on the number of database/server combinations and the frequency of using them, but if there are a lot of database/servers and low use frequency, using plain JDBC without Hibernate and data sources might be an option. I don't think Hibernate is meant for situations like that.
or extend org.apache.commons.dbcp.BasicDataSource
public class MyDataSource extends BasicDataSource {
private void init() {
username = ...
password = ...
url = ...
}
#Override
protected synchronized DataSource createDataSource() throws SQLException {
init();
return super.createDataSource();
}
}

Categories

Resources