I am in the middle of changing my spring + hibernate + mysql setup to be multi-tenant. First of all, I have the following in my application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
I am not sure if I'm supposed to make it connect to a specific schema here (test) after introducing multi-tenancy? It doesn't make sense to me, as it's supposed to use a default schema if no tenant is provided, otherwise connect to the schema associated with the tenant. However, if I remove it I get an error that no database was provided.
Secondly, the multi-tenant part doesn't seem to be working. All my queries are made in the test schema. I have implemented the following MultiTenantConnectionProvider:
#Component
public class TenantConnectionProvider implements MultiTenantConnectionProvider {
private Datasource datasource;
public TenantConnectionProvider(DataSource datasource) {
this.datasource = datasource;
}
...
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
logger.info("Get connection for tenant {}", tenantIdentifier);
final Connection connection = getAnyConnection();
connection.setSchema(tenantIdentifier);
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
logger.info("Release connection for tenant {}", tenantIdentifier);
connection.setSchema(DEFAULT_TENANT);
releaseAnyConnection(connection);
}
}
I am getting no errors, and when I make a query it correctly prints Get connection for tenant with the correct tenantIdentifier that matches the name of one of my other schemas. Still, it queries the test schema. What am I missing? Thanks!
EDIT:
It seems like connection.setSchema(tenantIdentifier) has no effect. In the method's description, it says the following: If the driver does not support schemas, it will silently ignore this request. So I'm guessing my driver does not support it? What to do in that case?
Using connection.createStatement().execute("USE " + tenantIdentifier); instead of connection.setSchema(tenantIdentifier); solved my problem.
Related
Now the project is using springmvc+ spring + mybatis + druid + postgresql
The users in the project correspond to the users in the database, so each time you run SQL, you switch the users with the (set role user) command and then perform the crud operations of the database.
My question:
Because there are many connections in the connection pool, the first step is to get the connection of the database, then switch users, and then perform the operation of business SQL on the database. But I don't know which part of the project this logic should be processed, because the connection of the connection pool and the execution of SQL are implemented by the underlying code. Do you have any good plans?
Can you provide me with a complete demo, such as the following operations:
Step 1, get the user's name from spring security (or shiro).
Step 2, Get the connection currently using the database from the connection pool.
Step 3, execute SQL (set role user) to switch roles.
Step 4, perform crud operation.
Step 5, Reset the database connection(reset role)
Here is a simple way to do what you need with the help of mybatis-spring.
Unless you already use mybatis-spring the first step would be to change the configuration of your project so that you obtain SqlSessionFactory using org.mybatis.spring.SqlSessionFactoryBean provided by mybatis-spring.
The next step is the implementation of setting/resetting the user role for the connection. In mybatis the connection lifecycle is controlled by the class implementing org.apache.ibatis.transaction.Transaction interface. The instance of this class is used by the query executor to get the connection.
In a nutshell you need to create your own implementation of this class and configure mybatis to use it.
Your implementation can be based on the SpringManagedTransaction from mybatis-spring and would look something like:
import org.springframework.security.core.Authentication;
class UserRoleAwareSpringManagedTransaction extends SpringManagedTransaction {
public UserRoleAwareSpringManagedTransaction(DataSource dataSource) {
super(dataSource);
}
#Override
public Connection getConnection() throws SQLException {
Connection connection = getCurrentConnection();
setUserRole(connection);
return connection;
}
private Connection getCurrentConnection() {
return super.getConnection();
}
#Override
public void close() throws SQLException {
resetUserRole(getCurrentConnection());
super.close();
}
private void setUserRole(Connection connection) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
Statement statement = connection.createStatement();
try {
// note that this direct usage of usernmae is a subject for SQL injection
// so you need to use the suggestion from
// https://stackoverflow.com/questions/2998597/switch-role-after-connecting-to-database
// about encoding of the username
statement.execute("set role '" + username + "'");
} finally {
statement.close();
}
}
private void resetUserRole(Connection connection) {
Statement statement = connection.createStatement();
try {
statement.execute("reset role");
} finally {
statement.close();
}
}
}
Now you need to configure mybatis to use you Transaction implementation. For this you need to implement TransactionFactory similar to org.mybatis.spring.transaction.SpringManagedTransactionFactory provided by mybatis-spring:
public class UserRoleAwareSpringManagedTransactionFactory implements TransactionFactory {
#Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new UserRoleAwareSpringManagedTransaction(dataSource);
}
#Override
public Transaction newTransaction(Connection conn) {
throw new UnsupportedOperationException("New Spring transactions require a DataSource");
}
#Override
public void setProperties(Properties props) {
}
}
And then define a bean of type UserRoleAwareSpringManagedTransactionFactory in your spring context and inject it into transactionFactory property of the SqlSessionFactoryBeen in your spring context.
Now every time mybatis obtains a Connection the implementation of Transaction will set the current spring security user to set the role.
Best practice is that database users are applications. Application users' access to particular data/resource should be controlled in the application. Applications should not rely on database to restrict data/resource access. Therefore, application users should not have different roles in database. An application should use only a single database user account.
Spring is manifestation of best practices. Therefore, Spring does not implement this functionality. If you want such functionality, you need to hack.
Referring to this, your best bet is to:
#Autowired JdbcTemplate jdbcTemplate;
// ...
public runPerUserSql() {
jdbcTemplate.execute("set role user 'user_1';");
jdbcTemplate.execute("SELECT 1;");
}
I still do not have much confidence in this. Unless you are writing a pgAdmin webapp for multiple users, you should re-consider your approach and design.
I'm using Spring JdbcTemplate on one of my projects and now, when there are really very much requests with it - I started to face this exception:
org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback;
uncategorized SQLException for SQL [{? = call API.get_data_for_nb(?, ?)}];
SQL state [99999]; error code [17009]; Closed Statement;
nested exception is java.sql.SQLException: Closed Statement
So the Closed Statement exception is received when you try to execute statement that is already closed, but in my case I don't close it by myself - I use JdbcTemplate exactly for that. So, firstly, what could be the reason for that?
The JdbcTemplate object itself is contained in #Stateless EJB in this way:
#Stateless(name = "NbEdwServiceEJB")
public class NbEdwServiceBean implements NbEdwServiceLocal, NbEdwServiceRemote {
#Resource(mappedName = JNDI)
private DataSource dataSource;
private static volatile JdbcTemplate jdbcTemplate;
#PostConstruct
protected void construct() {
synchronized (NbEdwServiceBean.class) {
if (jdbcTemplate == null) {
jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
}
}
}
private String getDataFromDB(final String request, final int isDigitalSignVerified) {
String response = null;
try {
response = jdbcTemplate.execute(SQL_GET_DATA, new CallableStatementCallback<String>() {
public String doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
cs.registerOutParameter(1, Types.VARCHAR);
cs.setInt(2, isDigitalSignVerified);
cs.setString(3, request);
cs.executeUpdate();
return cs.getString(1);
}
});
} catch (DataAccessException ex) {
LOGGER.error("getDataFromDB()", ex);
}
return response;
}
}
I know that this is maybe not the strictly right way to do it, I could just create instance of JdbcTemplate for every stateless bean - so I might do just that. So, secondly, why is this ever happening? My suppose was that JdbcTemplate's execute method isn't thread safe, but can someone give the full explanation on what is going on?
I have JEE version 5 running on WebLogic 10.3.5 if it's matter.
#Tolegen Izbassar I'm sorry that you're stuck with EE5.
Concerning the Singleton and EE5 there were some alternatives out there. One is vendor specific extensions, for example JBoss 5.x had service beans providing Singleton+JMX. A second solution is to use a earlier version of Jboss Seam compatible with EE5. A third alternative is to use the ServerContext from the Servlet API.
What you're trying to do in #PostConstuct is definitely not good. Non final statics in SLSB is a no go.
I suggest to have a look, at section 29.3 from Spring framework reference which describes EJB - Spring integration, an example from that section:
#Stateless
#Interceptors(SpringBeanAutowiringInterceptor.class)
public class MyFacadeEJB implements MyFacadeLocal {
// automatically injected with a matching Spring bean
#Autowired
private MyComponent myComp;
// for business method, delegate to POJO service impl.
public String myFacadeMethod(...) {
return myComp.myMethod(...);
}
This is my connection detail in JBoss standalone.xml
<connection-url>
jdbc:oracle:thin:#(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=xx.1xx.119.1xx)(PORT=1521))(LOAD_BALANCE=on)(FAILOVER=on))(CONNECT_DATA=(SERVICE_NAME=XE)))
</connection-url>
I want to handle a corner case of failover where post getting EntityManager object during a call of persist(), the connection is lost. Failover option is not switching to next database in the same transaction, it switches to active connection in the next transaction. I attempted something like this: (Catch Exception and get updated bean object)
public EntityManager getEntityManager() {
try {
entityManager = getEntityManagerDao(Constant.JNDI_NFVD_ASSURANCE_ENTITY_MANAGER);
} catch (NamingException e) {
LOGGER.severe("Data could not be persisted.");
throw new PersistenceException();
}
return entityManager.getEntityManager();
}
/**
* Inserts record in database. In case multiple connections/databases exist, one more attempt will be made to
* insert record.
*
* #param entry
*/
public void persist(Object entry) {
try {
getEntityManager().persist(entry);
} catch (PersistenceException pe) {
LOGGER.info("Could not persist data. Trying new DB connection.");
getEntityManager().persist(entry);
}
}
private static Object getJNDIObject(String path) throws NamingException {
Object jndiObject = null;
InitialContext initialContext = new InitialContext();
jndiObject = initialContext.lookup(path);
return jndiObject;
}
private static AssuranceEntityManager getEntityManagerDao(String path) throws NamingException {
return (AssuranceEntityManager) getJNDIObject(path);
}
But this one also is not helping. After catching the exception, getting a new bean with JNDI lookup does not contain an updated new connection and an exception is thrown. This results in loss of data of that transaction.
Please suggest how to handle this corner case of "Connection lost post getting EntityManager and before persisting."
I think it's quite impossible what you want to achieve. The thing is that if internal DB transction is aborted then the JTA transaction is in abort state and you can't continue with it.
I expect it's kind of similar to this case
#Stateless
public class TableCreator {
#Resource
DataSource datasource;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void create() {
try(Connection connection = datasource.getConnection()) {
Statement st = connection.createStatement();
st.execute("CREATE TABLE user (id INTEGER NOT NULL, name VARCHAR(255))");
} catch (SQLException sqle) {
// ignore this as table already exists
}
}
}
#Stateless
public class Inserter {
#EJB
private TableCreator creator;
public void call() {
creator.create();
UserEntity entity = new UserEntity(1, "EAP QE");
em.persist(entity);
}
}
In case that table user exists and you would use annotation #TransactionAttribute(TransactionAttributeType.REQUIRED) then the create call will be part of the same jta global transaction as call of persist. As in such case the transaction was aborted the persist call would fail with exception like (postgresql case)
Caused by: org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block
I mean if Oracle jdbc driver is not able to to handle connection fail transparently to JBoss app server and throws the exception upwards then I think that the only possible solution is to repeat the whole update action.
I'm currently facing the following known issue : https://jira.spring.io/browse/SWF-1525
I using Oracle9 and Ikaricp as connection pool. (At first i thought it was an issue with apache dbcp, this is why i switched to ikaricp)
I'm not using JPA but i tried to adapt one of the given workaround to the HibernateFlowExecutionListener.
Here is the code :
public class FixedHibernateFlowExecutionListener extends HibernateFlowExecutionListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
public FixedHibernateFlowExecutionListener(SessionFactory sessionFactory, PlatformTransactionManager transactionManager) {
super(sessionFactory, transactionManager);
}
#Override
public void paused(RequestContext context) {
super.paused(context);
if (isPersistenceContext(context.getActiveFlow())) {
final Session session = getSession(context.getFlowExecutionContext().getActiveSession());
if (session != null && session.isConnected()) {
session.disconnect();
if (session.isConnected()) {
logger.error("Couldn't disconnect the connection from the session");
}
}
}
}
private boolean isPersistenceContext(FlowDefinition flow) {
return flow.getAttributes().contains(PERSISTENCE_CONTEXT_ATTRIBUTE);
}
private Session getSession(FlowSession session) {
return (Session) session.getScope().get(PERSISTENCE_CONTEXT_ATTRIBUTE);
}}
The problem (beside the bug in SWF) is that calling 'session.disconnect();' never disconnects the session from the connection so the connection remains in use.
The lazy init is triggered in a subflow in 10% of the cases, in the 'on-start' tag of the subflow using Hibernate.initialize() on each collection item.
I have to find a fix for this because this is a very heavy operation that must not necessarily be done.
My hibernate properties :
hibernate.connection.release_mode=after_transaction
hibernate.temp.use_jdbc_metadata_defaults=false
hibernate.default_schema=*****
hibernate.dialect=org.hibernate.dialect.Oracle9iDialect
hibernate.id.new_generator_mappings=true
hibernate.event.merge.entity_copy_observer=allow
Has anyone found a solution for this?
Note : there was a similar question but related to jpa Database connections not being closed with jpaFlowExecutionListener
Thanks for help.
I am attempting to configure a UCP PoolDataSourceImpl with a ConnectionInitializationCallback. Here's my configuration:
private static DataSource createDataSource(Properties properties) throws SQLException, UniversalConnectionPoolException {
UniversalConnectionPoolManager ucpm = UniversalConnectionPoolManagerImpl.getUniversalConnectionPoolManager();
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
final String editionName = properties.getProperty("jdbc.editionName", "ora$base");
pds.registerConnectionInitializationCallback(new oracle.ucp.jdbc.ConnectionInitializationCallback() {
public void initialize(Connection connection) throws SQLException {
LOG.debug("Attempting to set edition to: {}", editionName);
try (Statement statement = connection.createStatement()) {
statement.executeUpdate("ALTER SESSION SET EDITION = " + editionName);
}
LOG.debug("Edition set to: {}", editionName);
}
});
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setUser(properties.getProperty("jdbc.username"));
pds.setPassword(properties.getProperty("jdbc.password"));
pds.setURL(properties.getProperty("jdbc.url"));
pds.setConnectionPoolName("demo-pool");
pds.setInitialPoolSize(3);
pds.setMaxPoolSize(3);
pds.setValidateConnectionOnBorrow(true);
LOG.debug("Created DataSource Pool");
ucpm.createConnectionPool((UniversalConnectionPoolAdapter)pds);
ucpm.startConnectionPool("demo-pool");
return pds;
}
However the initialize method is never called. I'm using java 1.7.0_51 with the following Oracle jars:
ojdbc6.jar - v12.1.0.1.0
ucp.jar - v12.1.0.0.0
I have managed to make this work by removing the call to "registerConnectionInitializationCallback" and replacing it with a call to "registerConnectionLabelingCallback" but from my understanding this will exeute the ALTER SESSION each time a connection is requested from the pool rather then when it is actually created.
Any help with getting the ConnectionInitializationCallback to work would be much appreciated.
Kind Regards
The ConnectionInitializationCallback mechanism appears to be a part of the "Application Continuity" feature released with 12c. Application Continuity requires that you use one of the following DataSource implementations:
oracle.jdbc.replay.OracleDataSourceImpl
oracle.jdbc.replay.OracleConnectionPoolDataSourceImpl
I haven't tried it for myself, but I'm guessing if you use one of these DataSource implementations, your ConnectionInitializationCallback will work.
It would've been nice if this had been included as part of the standard UCP implementation, much like Connection Labeling.