I have a multi-tenant application where one DB per tenant with one Master DB is configured. I load all the data sources in applications something like this :
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
public DataSource dataSource() {
if(LOGGER.isInfoEnabled())
LOGGER.info("Loading datasources ...");
DataSource ds = null;
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
// load MASTER datasource
ds = dataSourceLookup.getDataSource(properties.getJndiName());
// load other TENANTs DB details
JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);
List<GroupConfig> groupConfigs = jdbcTemplate.query(
"select * from master.tblTenant where IsActive=1 and ConfigCode in ('DB_URL','DATASOURCE_CLASS','USER_NAME','DB_PASSWORD') order by 2",
new ResultSetExtractor<List<GroupConfig>>() {
public List<GroupConfig> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<GroupConfig> list = new ArrayList<GroupConfig>();
while (rs.next()) {
GroupConfig groupConfig = new GroupConfig();
groupConfig.setGroupConfigId(rs.getLong(1));
groupConfig.setGroupCode(rs.getString(2));
groupConfig.setConfigCode(rs.getString(3));
groupConfig.setConfigValue(rs.getString(4));
groupConfig.setIsActive(rs.getBoolean(5));
list.add(groupConfig);
}
return list;
}
});
int propCount = 1;
Map<String, Map<String, String>> groups = new HashMap<String, Map<String, String>>();
Map<String, String> temp = new HashMap<String, String>();
for (GroupConfig config : groupConfigs) {
temp.put(config.getConfigCode(), config.getConfigValue());
if (propCount % 4 == 0) {
groups.put(config.getGroupCode(), temp);
temp = new HashMap<String, String>();
}
propCount++;
}
// Create TENANT dataSource
Map<Object, Object> resolvedDataSources = new HashMap<Object, Object>();
for (String tenantId : groups.keySet()) {
Map<String, String> groupKV = groups.get(tenantId);
DataSourceBuilder dataSourceBuilder = new DataSourceBuilder(this.getClass().getClassLoader());
dataSourceBuilder.driverClassName(groupKV.get("DATASOURCE_CLASS")).url(groupKV.get("DB_URL"))
.username(groupKV.get("USER_NAME")).password(groupKV.get("DB_PASSWORD"));
//System.out.println(dataSourceBuilder.findType()); //class org.apache.tomcat.jdbc.pool.DataSource
if (properties.getType() != null) {
dataSourceBuilder.type(properties.getType());
}
if(LOGGER.isInfoEnabled())
LOGGER.info("Building datasource : "+tenantId);
resolvedDataSources.put(tenantId, dataSourceBuilder.build());
}
resolvedDataSources.put("MASTER", ds);
MultitenantDataSource dataSource = new MultitenantDataSource();
dataSource.setTargetDataSources(resolvedDataSources);
dataSource.setDataSourceLookup(dataSourceLookup);
dataSource.afterPropertiesSet();
if(LOGGER.isInfoEnabled())
LOGGER.info("Datasources initialization finished !");
return dataSource;
}
In controller I set respective data source as (similarly for TENANT datasources) :
TenantContext.setCurrentTenant("MASTER");
Issue : On server startup every thing works fine (both MASTER DB and TENANT specific queries), but once the server is idle for some time (few Hours) tenant specific calls starts failing (while MASTER DB connections still works fine) with error :
Could not open JPA EntityManager for transaction; nested exception is javax.persistence.PersistenceException: com.microsoft.sqlserver.jdbc.SQLServerException: The connection is closed.
Please help me to get rid off this exception. Thanks in advance.
I got the problem and solutions as well:
Why tenant connections was getting closed? Because Auto configurations(#ConfigurationProperties(prefix = "spring.datasource")) of spring-boot was not getting applied on the tenant DataSources which I was creating in code.
Resolution- I added new method to set the tomcat connection pool properties:
private DataSource buildDataSource(String driverClass, String url, String user, String pass){
PoolProperties p = new PoolProperties();
p.setUrl(url);
p.setDriverClassName(driverClass);
p.setUsername(user);
p.setPassword(pass);
p.setJmxEnabled(true);
p.setTestWhileIdle(false);
p.setTestOnBorrow(true);
p.setValidationQuery("SELECT 1");
p.setTestOnReturn(false);
p.setValidationInterval(30000);
p.setTimeBetweenEvictionRunsMillis(30000);
p.setMaxActive(100);
p.setInitialSize(10);
p.setMaxWait(10000);
p.setRemoveAbandonedTimeout(60);
p.setMinEvictableIdleTimeMillis(30000);
p.setMinIdle(10);
p.setLogAbandoned(true);
p.setRemoveAbandoned(true);
DataSource datasource = new DataSource();
datasource.setPoolProperties(p);
return datasource;
}
This solved my problem. But I'm curious to know if there is a way to apply AutoConfigurations while creating the objects in spring-boot.
Related
I have just started to use FluentMigration for my current project. I wrote my first migration but I have some trouble writing a unit test for it.
Here is some sample code:
private ServiceProvider CreateServiceProvider()
{
return new ServiceCollection()
.AddLogging(lb => lb.AddFluentMigratorConsole())
.AddFluentMigratorCore()
.ConfigureRunner(
builder => builder
.AddSQLite()
.WithGlobalConnectionString("Data Source=:memory:;Version=3;New=True;")
.WithMigrationsIn(typeof(MigrationOne).Assembly))
.BuildServiceProvider();
}
private void PerformMigrateUp(IServiceScope scope)
{
var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
runner.MigrateUp(1);
}
[Test]
public void ShouldHaveTablesAfterMigrateUp()
{
var provider = this.CreateServiceProvider();
using (var scope = provider.CreateScope())
{
this.PerformMigrateUp(scope);
// here I'd like to test if tables have been created in the database by the migration
}
}
I don't know how (or if it is possible) to access the current database connection so I can perform a query. Any suggestions would be helpful. Thanks.
Ok, I found a solution. I have to use the Process method of the runner's processor to perform my own sql query.
It looks like this:
private ServiceProvider CreateServiceProvider()
{
return new ServiceCollection()
.AddLogging(lb => lb.AddFluentMigratorConsole())
.AddFluentMigratorCore()
.ConfigureRunner(
builder => builder
.AddSQLite()
.WithGlobalConnectionString(#"Data Source=:memory:;Version=3;New=True;")
.WithMigrationsIn(typeof(MigrationDate20181026113000Zero).Assembly))
.BuildServiceProvider();
}
[Test]
public void ShouldHaveNewVersionAfterMigrateUp()
{
var serviceProvider = this.CreateServiceProvider();
var scope = serviceProvider.CreateScope();
var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
runner.MigrateUp(1);
string sqlStatement = "SELECT Description FROM VersionInfo";
DataSet dataSet = runner.Processor.Read(sqlStatement, string.Empty);
Assert.That(dataSet, Is.Not.Null);
Assert.That(dataSet.Tables[0].Rows[0].ItemArray[0], Is.EqualTo("Migration1"));
}
This is an old question but an important one. I find it strange that I couldnt find any documentation on this.
In any case here is my solution which I find to be a bit better as you dont need to rely on the runner. Since you dont need that the options open up hugely for constructor arguments.
Firstly make sure you install Microsoft.Data.Sqlite or you will get a strange error.
SQLite in memory databases exist for as long as the connection does - and 1 database per connection on first glance. Actually though there is a way to share the database between connections as long as at least 1 connection is open at all times according to my experiments. You just need to name it.
https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/connection-strings#sharable-in-memory
So to begin with I created a connection that will stay open until the test finishes. It will be named using Guid.NewGuid() so that subsequent connections will work as expected.
var dbName = Guid.NewGuid().ToString();
var connectionString = $"Data Source={dbName};Mode=Memory;Cache=Shared";
var connection = new SqliteConnection(connectionString);
connection.Open();
After that the crux of running the migrations is the same as previously answered but the connection string uses the named database:
var sp = services.AddFluentMigratorCore()
.ConfigureRunner(fluentMigratorBuilder => fluentMigratorBuilder
.AddSQLite()
.WithGlobalConnectionString(connectionString)
.ScanIn(AssemblyWithMigrations).For.Migrations()
)
.BuildServiceProvider();
var runner = sp.GetRequiredService<IMigrationRunner>();
runner.MigrateUp();
Here is a class I use to inject a connection factory everywhere that needs to connect to the database for normal execution:
internal class PostgresConnectionFactory : IConnectionFactory
{
private readonly string connectionString;
public PostgresConnectionFactory(string connectionString)
{
this.connectionString = connectionString;
}
public DbConnection Create()
{
return new NpgsqlConnection(connectionString);
}
}
I just replaced this (all hail dependency inversion) with:
internal class InMemoryConnectionFactory : IConnectionFactory
{
private readonly string connectionstring;
public InMemoryConnectionFactory(string connectionstring)
{
this.connectionstring = connectionstring;
}
public DbConnection Create()
{
return new SqliteConnection(connectionstring);
}
}
where the connection string is the same named one I defined above.
Now you can simply use that connection factory anywhere that needs to connect to the same in memory database, and since we can now connect multiple times possibilities for integration testing open up.
Here is the majority of my implementation:
public static IDisposable CreateInMemoryDatabase(Assembly AssemblyWithMigrations, IServiceCollection services = null)
{
if (services == null)
services = new ServiceCollection();
var connectionString = GetSharedConnectionString();
var connection = GetPersistantConnection(connectionString);
MigrateDb(services, connectionString, AssemblyWithMigrations);
services.AddSingleton<IConnectionFactory>(new InMemoryConnectionFactory(connectionString));
return services.BuildServiceProvider()
.GetRequiredService<IDisposableUnderlyingQueryingTool>();
}
private static string GetSharedConnectionString()
{
var dbName = Guid.NewGuid().ToString();
return $"Data Source={dbName};Mode=Memory;Cache=Shared";
}
private static void MigrateDb(IServiceCollection services, string connectionString, Assembly assemblyWithMigrations)
{
var sp = services.AddFluentMigratorCore()
.ConfigureRunner(fluentMigratorBuilder => fluentMigratorBuilder
.AddSQLite()
.WithGlobalConnectionString(connectionString)
.ScanIn(assemblyWithMigrations).For.Migrations()
)
.BuildServiceProvider();
var runner = sp.GetRequiredService<IMigrationRunner>();
runner.MigrateUp();
}
private static IDbConnection GetPersistantConnection(string connectionString)
{
var connection = new SqliteConnection(connectionString);
connection.Open();
return connection;
}
Then here is a sample test:
public Test : IDisposable {
private readonly IDisposable _holdingConnection;
public Test() {
_holdingConnection = CreateInMemoryDatabase(typeof(MyFirstMigration).Assembly);
}
public void Dispose() {
_holdingConnection.Dispose();
}
}
You may notice that the static factory returns a custom interface. Its just an interface that extends the normal tooling I inject to repositories, but also implements IDisposable.
Untested bonus for integration testing where you will have a service collection created via WebApplicationFactory or TestServer etc:
public void AddInMemoryPostgres(Assembly AssemblyWithMigrations)
{
var lifetime = services.BuildServiceProvider().GetService<IHostApplicationLifetime>();
var holdingConnection= InMemoryDatabaseFactory.CreateInMemoryDapperTools(AssemblyWithMigrations, services);
lifetime.ApplicationStopping.Register(() => {
holdingConnection.Dispose();
});
}
I'm executing queries periodically (by a scheduler) using my Spring Boot application
application.properties
src_mssqlserver_url=jdbc:sqlserver://192.168.0.1;databaseName=Test;
src_mssqlserver_username=tester
src_mssqlserver_password=tester1
src_mssqlserver_driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
Datasource and JdbcTemplate Bean
#Primary
#Bean(name = "src_mssqlserver")
#ConfigurationProperties(prefix = "spring.ds_mssqlserver")
public DataSource srcDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("src_mssqlserver_driverClassName"));
dataSource.setUrl(env.getProperty("src_mssqlserver_url"));
dataSource.setUsername(env.getProperty("src_mssqlserver_username"));
dataSource.setPassword(env.getProperty("src_mssqlserver_password"));
return dataSource;
}
#Bean(name = "srcJdbcTemplate")
public JdbcTemplate srcJdbcTemplate(#Qualifier("src_mssqlserver") DataSource dsSrcSqlServer) {
return new JdbcTemplate(dsSrcSqlServer);
}
Usage: This method is called from a scheduler with list of items to process (normally 1000 records), this process runs in an hour once.
#Autowired
#Qualifier("srcJdbcTemplate")
private JdbcTemplate srcJdbcTemplate;
public void batchInsertUsers(final List<User> users) {
String queryInsert = "INSERT INTO [User] ([Name]"
+ " , [Created_Date]"
+ " , [Notes])"
+ " VALUES (?, SYSDATETIMEOFFSET(), ?)";
srcJdbcTemplate.batchUpdate(queryInsert, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
User user = users.get(i);
ps.setString(1, user.getName());
ps.setString(2, user.getNotes());
}
#Override
public int getBatchSize() {
return sites.size();
}
});
I'm getting warnings from database administrator that my code keeping too much connections open. Please share some standard and workable way to handle such situation.
Thanks.
DriverManagerDataSource is NOT meant for production, it opens and closes a connection each time it needs one.
Use a connection pool like c3p0DataSource.
So our current code loaded a csv file into a local jdbcTemplate in which then I do some querying. The issue was always performance, and we finally got access to a SQL server that could load the data. Naturally the company gets the guy with basically no database skills to set this up :P
#Autowired
DataSource dataSource;
#RequestMapping("/queryService")
public void queryService(#RequestParam("id")String id)
{
log.info("Creating tables");
jdbcTemplate.execute("DROP TABLE accounts IF EXISTS");
jdbcTemplate.execute("CREATE TABLE accounts(id VARCHAR(255), name VARCHAR(255), Organization__c VARCHAR(255)";
insertBatch(accounts,dataSource);
ArrayList<Account2> filteredaccs = filterAccount(jdbcTemplate);
.
public void insertBatch(ArrayList<Account2> accs, DataSource dataSource) {
List<Map<String, Object>> batchValues = new ArrayList<>(accs.size());
for (Account2 a : accs) {
Map<String, Object> map = new HashMap<>();
map.put("id", a.getId());
map.put("name", a.getName());
map.put("Organization__c", a.getOrganization__c());
batchValues.add(map);
}
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("accounts");
int[] ints = simpleJdbcInsert.executeBatch(batchValues.toArray(new Map[accs.size()]));
}
.
public ArrayList<Account2> filterAccount(JdbcTemplate jdbcTemplate)
{
String sql= "query string";
ArrayList<Account2> searchresults = (ArrayList<Account2>) jdbcTemplate.query(sql,
new RowMapperResultSetExtractor<Account2>(new AccountRowMapper(), 130000));
return searchresults;
}
.
public class AccountRowMapper implements RowMapper<Account2> {
public Account2 mapRow(ResultSet rs, int rowNum) throws SQLException {
Account2 a = new Account2();
a.setId(rs.getString("id"));
a.setName(rs.getString("name"));
a.setOrganization__c(rs.getString("Organization__c"));
return a;
}
}
The question here is what is the quickest way for me to 'switch' over to using a SQL server to pull the data down, with the same table and rows, without changing too much of my current code?
I am working on an MVC3 application database first approach . I would like to use one connection string to connect to database, based on some string (company name). Example: I have in my MSSQL Express 2012 this db: my_database_microsoft, my_database_oracle and so on..(those databases have same structure). On login page I have 3 input fields: username,password,company. I know how to build connection string dynamic with SqlConnectionStringBuilder and then use it on EntityConnectionStringBuilder
string providerName = "System.Data.SqlClient";
string serverName = "MY-PC\\SQL2012";
string databaseName = "my_database_"+form[company].toString();
.....
.....
entityBuilder.Provider = providerName;
// Set the provider-specific connection string.
entityBuilder.ProviderConnectionString = providerString;
// Set the Metadata location.
entityBuilder.Metadata =#"res://*/Models.Model1.csdl|res://*/Models.Model1.ssdl|res://*/Models.Model1.msl";
using (EntityConnection conn =
new EntityConnection(entityBuilder.ToString()))
{
conn.Open();
// Console.WriteLine("Just testing the connection.");
conn.Close();
}
obracun_placEntities1.nameOrConnectionString = entityBuilder.ToString();
obracun_placEntities1 o = new obracun_placEntities1(entityBuilder.ToString());
I have made a partial class of my entety and give a constructor that take a nameOrConnectionString string as a parameter.
public partial class obracun_placEntities1
{
public string nameOrConnectionString { get; set; }
public obracun_placEntities1(string nameOrConnectionString)
: base(nameOrConnectionString ?? "obracun_placEntities1") { }
}
This works only in loginController but how can I use this in UsersController and all other controllers where I using obracun_placEntities1 db = new obracun_placEntities1(); > this take the default database from web.config. I would not like to save connection string to session or cookie and than pass it in every controler as a parameter.
private obracun_placEntities1 db = new obracun_placEntities1();
How can i achieve that i pass connection string in login controller and using this database in entire project.
One more problem occured when i want to use public static string nameOrConnectionString
and pass it to constructor. The problem is when I open application in Chrome and login as user1 I get all infromation from user1 database, but then I login in MS Explorere as user2 and get all data from user2 database. When i refresh chrome I get information from the user2 database not user1.
Model1.context.cs
public partial class obracun_placEntities1 : DbContext
{
public static string nameOrConnectionString { get; set; }
// public static string connection;
public obracun_placEntities1()
: base(nameOrConnectionString ?? "obracun_placEntities1")
{
}
Connecting to different Databases is best done using the DBconnection constructor on DBCOntext. If you look at the DBContext class you will see multiple constructor overloads. One allows the DBConnection to be supplied. So no entry in WEB.Config/App.Config is required.
See this post with sample code Same Context accessing different databases.
EDIT sample added:
public partial class obracun_placEntities1 : DbContext
{
// use THIS CONSTRUCTOR
protected obracun_placEntities1(DbConnection dbConnection, bool contextOwnsConnection)
: base(dbConnection, contextOwnsConnection)
{
}
}
}
// DONT USE THIS
// obracun_placEntities1.nameOrConnectionString = entityBuilder.ToString();
// obracun_placEntities1 o = new obracun_placEntities1(entityBuilder.ToString());`
// build the connection - note: it is NOT a connection string. it is a DBConnection!
conn = getDBConnection4SQLServer(DatabaseServer,Databasename)
obracun_placEntities1 o = new obracun_placEntities1(conn,true);
//====================================================================
public const string DefaultDataSource = "localhost";
public DbConnection getDBConnection4SQLServer(string dataSource, string dbName) {
var sqlConnStringBuilder = new SqlConnectionStringBuilder();
sqlConnStringBuilder.DataSource = String.IsNullOrEmpty(dataSource) ? DefaultDataSource : dataSource;
sqlConnStringBuilder.IntegratedSecurity = true;
sqlConnStringBuilder.MultipleActiveResultSets = true;
var sqlConnFact = new SqlConnectionFactory(sqlConnStringBuilder.ConnectionString);
var sqlConn = sqlConnFact.CreateConnection(dbName);
return sqlConn;
}
I finnaly getting it to work with this code.
My LoginController
[HttpPost]
public ActionResult Index(UPORABNIK model, FormCollection form)
{....}
public obracun_placEntities1(EntityConnection entityConnection)
: base(entityConnection, false)
{
}
I call EntityConnection conn = GetEntityConnDbName("ServerName", "FirmName").
_entities = new obracun_placEntities1(conn, false);
var uporabniki = from r in _entities.UPORABNIK.Where(r => r.ime == uporabnik && r.geslo == geslo && danes <= r.veljavnost).ToList()
select r;
I get the firmName from the post form so this work only in LoginController.But how can I use this constructor in all other Controllers? I get firm name only once in LoginControler, I tried to save it as cookie but then i can not read it in the constructor.
In my other Controller I use the default controller again. How can I tranfer conn to other Controllers?
private obracun_placEntities1 db = new obracun_placEntities1();
I would like to call like this
private obracun_placEntities1 db = new obracun_placEntities1(conn);
Or is there some better way?
I have a Java EE struts web application using a singleton database connection. In the past, there is only one weblogic server, but now, there are two weblogic servers in a cluster.
Session replication have been tested to be working in this cluster. The web application consist of a few links that will open up different forms for the user to fill in. Each form has a dynamic dropdownlist that will populate some values depending on which form is clicked. These dropdownlist values are retrieved from the oracle database.
One unique issue is that the first form that is clicked, might took around 2-5 seconds, and the second form clicked could take forever to load or more than 5 mins. I have checked the codes and happened to know that the issue lies when an attempt to call the one instance of the db connection. Could this be a deadlock?
public static synchronized DataSingleton getDataSingleton()
throws ApplicationException {
if (myDataSingleton == null) {
myDataSingleton = new DataSingleton();
}
return myDataSingleton;
}
Any help in explaining such a scenario would be appreciated.
Thank you
A sample read operation calling Singleton
String sql = "...";
DataSingleton myDataSingleton = DataSingleton.getDataSingleton();
conn = myDataSingleton.getConnection();
try {
PreparedStatement pstmt = conn.prepareStatement(sql);
try {
pstmt.setString(1, userId);
ResultSet rs = pstmt.executeQuery();
try {
while (rs.next()) {
String group = rs.getString("mygroup");
}
} catch (SQLException rsEx) {
throw rsEx;
} finally {
rs.close();
}
} catch (SQLException psEx) {
throw psEx;
} finally {
pstmt.close();
}
} catch (SQLException connEx) {
throw connEx;
} finally {
conn.close();
}
The Singleton class
/**
* Private Constructor looking up for Server's Datasource through JNDI
*/
private DataSingleton() throws ApplicationException {
try {
Context ctx = new InitialContext();
SystemConstant mySystemConstant = SystemConstant
.getSystemConstant();
String fullJndiPath = mySystemConstant.getFullJndiPath();
ds = (DataSource) ctx.lookup(fullJndiPath);
} catch (NamingException ne) {
throw new ApplicationException(ne);
}
}
/**
* Singleton: To obtain only 1 instance throughout the system
*
* #return DataSingleton
*/
public static synchronized DataSingleton getDataSingleton()
throws ApplicationException {
if (myDataSingleton == null) {
myDataSingleton = new DataSingleton();
}
return myDataSingleton;
}
/**
* Fetching SQL Connection through Datasource
*
*/
public Connection getConnection() throws ApplicationException {
Connection conn = null;
try {
if (ds == null) {
}
conn = ds.getConnection();
} catch (SQLException sqlE) {
throw new ApplicationException(sqlE);
}
return conn;
}
It sounds like you may not be committing the transaction at the end of your use of the connection.
What's in DataSingleton - is it a database connection? Allowing multiple threads to access the same database connection is not going to work, for example once you have more than one user. Why don't you use a database connection pool, for example a DataSource?