Can't alter Oracle session with JdbcTemplate - spring

I need to change the NLS_NUMERIC_CHARACTERS of my Oraclesession in my Spring-Standaloneapplication. I'm using JdbcTemplates:
logger.info("Setting NLS-Parameter.");
jdbcTemplate.execute("alter session set NLS_NUMERIC_CHARACTERS='.,'");
logger.info(jdbcTemplate.queryForObject("select value from nls_session_parameters where parameter = 'NLS_NUMERIC_CHARACTERS'", String.class));
But it does not change:
Setting NLS-Parameter.
,.
I initialize the JdbcTemplate programatically:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(ORACLE_JDBC_DRIVER);
dataSource.setUrl(url);
dataSource.setUsername(schema);
dataSource.setPassword(password);
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
All other statements are working perfectly. Just altering the session does not work. The Database is Oracle 18 Standart.

The DriverManagerDataSource data source returns a new Connection from every getConnection call.
Thus, JdbcTemplate#execute(StatementCallback<T> action), which JdbcTemplate#execute(final String sql) and JdbcTemplate#queryForObject(String sql, Class<T> requiredType) ultimately both call, will get a newly created connection for each execution.
The alter session set NLS_NUMERIC_CHARACTERS='.,' statement changes NLS_NUMERIC_CHARACTERS for the session, so only for the connection that executes the statement. The change won't have an effect on other connections.

Related

connection.prepareStatement("alter session set container=YPDB2").executeUpdate() return 0;

use ojbc7 to connection oracle12c,
execute "alter session set container=ypdb2", it seems not work;
but i use sqlplus to execute, it is work;
here is my code;
OracleDataSource oracleDataSource = new OracleDataSource();
oracleDataSource.setURL("jdbc:oracle:thin:#127.0.0.1:1521/orcl");
Connection connection = oracleDataSource.getConnection("sys as sysdba", "123456");
PreparedStatement preparedStatement = connection.prepareStatement("alter session set container=YPDB2");
log.info("{}",preparedStatement.executeUpdate());
console print 0
it seems to affect the zero row;
Does this mean that the “alter” did not succeed?
it seems a bug of ojbc;
#Test
public void testAlterSession() throws SQLException {
OracleDataSource oracleDataSource = new OracleDataSource();
oracleDataSource.setURL("jdbc:oracle:thin:#127.0.0.1:1521/ypdb9");
Connection sys_as_sysdba_connection = oracleDataSource.getConnection("sys as sysdba", "123456");
int i = sys_as_sysdba_connection.createStatement().executeUpdate("alter session set container=ypdb9");
log.info("i:{}",i);
List<Map<String, Object>> maps = OdbcUtil.resultSetToList(sys_as_sysdba_connection.createStatement().executeQuery("select * from v$pdbs"));
maps.forEach(map->log.info("{}",map));
sys_as_sysdba_connection.createStatement().executeUpdate("alter session set container=cdb$root");
List<Map<String,Object>> maps2 = OdbcUtil.resultSetToList(sys_as_sysdba_connection.createStatement().executeQuery("select * from v$pdbs"));
maps2.forEach(map->log.info("{}",map));
}
In this test, "excuteUpdate(sql)" returns 0,but the "container" does change when I select the pdb again;
i don't know why yet;

spring data jpa (hibernate+spring+jpa)

I got one requirement where each company has separate database with the same schema, in future new company might be added to same schema structure.I have to connect their company schema based on user login. So that I have to connect separate database each time when user login.I wan to use spring data JPA to take care of connection.Only I will configure new schema credential in properties file.Is there any way we can add dynamic database schema which uses same model classes.All should happen in run time only.
Sounds like you are asking about multi-tenant support. You can create a LocalContainerEntityManagerFactoryBean something like this:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProvider,
CurrentTenantIdentifierResolver tenantIdentifierResolver) {
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setDataSource(dataSource);
emfBean.setPackagesToScan(Application.class.getPackage().getName());
emfBean.setJpaVendorAdapter(jpaVendorAdapter());
Map<String, Object> jpaProperties = new HashMap<>();
jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
// Improved naming strategy deprecated as of hibernate 5.0
// jpaProperties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
jpaProperties.put("hibernate.implicit_naming_strategy", "org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl");
jpaProperties.put("hibernate.physical_naming_strategy", "com.example.config.HibernateLegacyImprovedNamingStrategy");
jpaProperties.put("hibernate.show_sql", "true");
jpaProperties.put("hibernate.format_sql", "true");
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifierResolver);
emfBean.setJpaPropertyMap(jpaProperties);
return emfBean;
}
And provide implementations of MultiTenantConnectionProvider and CurrentTenantIdentifierResolver
As an example, for schema tenancy, the CurrentTenantIdentifierResolver could look up the current user from session to determine what schema to use.
The MultiTenantConnectionProvider could alter the connection with a command like SELECT set_schema_to(?) before returning the connection for use (and reset it on return).
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
setSchemaTo(connection, tenantIdentifier);
return connection;
}
You'll need to read up on multi-tenancy and see what type you want along with which implementations you are using.

How to set autocommit to false in spring jdbc template

Currently I'm setting autocommit to false in spring through adding a property to a datasource bean id like below :
<property name="defaultAutoCommit" value="false" />
But i need to add it specifically in a single java method before executing my procedure.
I used the below code snippet.
getJdbcTemplate().getDataSource().getConnection().setAutoCommit(false);
But the above line was not setting autocommit to false?
Am i missing anything ?
or any alternative to set autocommit in a specific java method by spring
Thanks
The problem is that you are setting autocommit on a Connection, but JdbcTemplate doesn't remember that Connection; instead, it gets a new Connection for each operation, and that might or might not be the same Connection instance, depending on your DataSource implementation. Since defaultAutoCommit is not a property on DataSource, you have two options:
Assuming your concrete datasource has a setter for defaultAutoCommit (for example, org.apache.commons.dbcp.BasicDataSource), cast the DataSource to your concrete implementation. Of course this means that you can no longer change your DataSource in your Spring configuration, which defeats the purpose of dependency injection.
((BasicDataSource)getJdbcTemplate().getDataSource()).setDefaultAutoCommit(false);
Set the DataSource to a wrapper implementation that sets AutoCommit to false each time you fetch a connection.
final DataSource ds = getJdbcTemplate().getDataSource();
getJdbcTemplate().setDataSource(new DataSource(){
// You'll need to implement all the methods, simply delegating to ds
#Override
public Connection getConnection() throws SQLException {
Connection c = ds.getConnection();
c.setAutoCommit(false);
return c;
}
});
You need to get the current connection. e.g.
Connection conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
try {
conn.setAutoCommit(false);
/**
* Your Code
*/
conn.commit();
} catch (SQLException e) {
conn.rollback();
e.printStackTrace();
}
I'm posting this because I was looking for it everywhere: I used configuration property in Spring boot to achieve setting the default autocommit mode with:
spring.datasource.hikari.auto-commit: false
Spring Boot 2.4.x Doc for Hikari
You will have to do for each statement that the jdbcTemplate executes. Because for each jdbcTemplate.execute() etc it gets a new connection from the Datasource's connection pool. So you will have to set it for the connection that the connection the jdbcTemplate uses for that query. So you will have to do something like
jdbcTemplate.execute("<your sql query", new PreparedStatementCallback<Integer>(){
#Override
public Integer doInPreparedStatement(PreparedStatement stmt) throws SQLException, DataAccessException
{
Connection cxn = stmt.getConnection();
// set autocommit for that cxn object to false
cxn.setAutoCommit(false);
// set parameters etc in the stmt
....
....
cxn.commit();
// restore autocommit to true for that cxn object. because if the same object is obtained from the CxnPool later, autocommit will be false
cxn.setAutoCommit(true);
return 0;
}
});
Hope this helps
after 5 years still a valid question, i resolved my issue in this way :
set a connection with connection.setAutoCommit(false);
create a jbc template with that connection;
do your work and commit.
Connection connection = dataSource.getConnection();
connection.setAutoCommit(false);
JdbcTemplate jdbcTemplate =
new JdbcTemplate(newSingleConnectionDataSource(connection, true));
// ignore case in mapping result
jdbcTemplate.setResultsMapCaseInsensitive(true);
// do your stuff
connection.commit();
I just came across this and thought the solution would help someone even if it's too late.
As Yosef said, the connection that you get by calling getJdbcTemplate().getDataSource().getConnection() method may or may not be the one used for the communication with database for your operation.
Instead, if your requirement is to just test your script, not to commit the data, you can have a Apache Commons DBCP datasource with auto commit set to fault. The bean definition is given below:
/**
* A datasource with auto commit set to false.
*/
#Bean
public DataSource dbcpDataSource() throws Exception {
BasicDataSource ds = new BasicDataSource();
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
ds.setDefaultAutoCommit(false);
ds.setEnableAutoCommitOnReturn(false);
return ds;
}
// Create either JdbcTemplate or NamedParameterJdbcTemplate as per your needs
#Bean
public NamedParameterJdbcTemplate dbcpNamedParameterJdbcTemplate() throws Exception {
return new NamedParameterJdbcTemplate(dbcpDataSource());
}
And use this datasource for any such operations.
If you wish to commit your transactions, I suggest you to have one more bean of the datasource with auto commit set to true which is the default behavior.
Hope it helps someone!
I needed it to do some unit testing
In fact Spring already provides the SingleConnectionDataSource implementation with the setAutoCommit method
// import org.springframework.jdbc.datasource.SingleConnectionDataSource;
SingleConnectionDataSource dataSource = new SingleConnectionDataSource();
dataSourceRX71.setAutoCommit(false);
dataSourceRX71.setDriverClassName("xxx");
dataSourceRX71.setUrl("xxx");
dataSourceRX71.setUsername("xxx");
dataSourceRX71.setPassword("xxx");
In some case you could just add #Transactional in the method, e.g. After some batch insert, execute commit at last.

Spring JDBC connection without dataSource

I had read several articles on obtaining Connection using Spring DataSource.
But in our Company Setup, connection object is obtained through already configured environment. Following the sample code:
String pool = PropertyFileReader.getPropertyValue("database.properties", "development.connectionPool");
Connection connection = RequestUtils.getConnection(pool);
Hence, After reading this tutorial
I am confused on using JDBCTemplate using connection object from above code.
I believe JdbcTemplate is not designed to work against a Connection as what you expected. As a workaround, if you are fine to create a separate JdbcTemplate for each connection you created, you may wrap your connection in a thin wrapper of DataSource, and feed it to JdbcTemplate.
I think it should work but I haven't tried it anyway...
class SingleConnectionDataSource implements DataSource {
private Connection connection;
public SingleConnectionDataSource(Connection connection) {
this.connection = connection;
}
public Connection getConnection() {
return this.connection;
}
public Connection getConnection(String username, String password) {
return this.connection;
}
}
// at the place you want to use JdbcTemplate
Connection conn = blablabla; // your own way to get it
JdbcTemplate jdbcTemplate = new JdbcTemplate(new SingleConnectionDataSource(conn));
Actually, Spring already provided SingleConnectionDataSource implementation (have seen in version 4.1.7).
It is even allows you to supress connection closing by template.

TransactionTemplate vs JdbcTemplate

The Spring framework provides two means of programmatic transaction management:
Using the TransactionTemplate.
Using a PlatformTransactionManager implementation directly.
The above is described here: http://static.springsource.org/spring/docs/2.0.8/reference/transaction.html
The Spring site hasnot mentioned JdbcTemplate here. As per my understanding JdbcTemplate also manages the transaction internally and this is all done in programme too.
So what's the basic difference between TransactionTemplate and JdbcTemplate?
JdbcTemplate is not a transaction manager. It's merely a helper class for native JDBC operations:
This is the central class in the JDBC core package. It simplifies the use of JDBC and helps to avoid common errors. It executes core JDBC workflow, leaving application code to provide SQL and extract results. This class executes SQL queries or updates, initiating iteration over ResultSets and catching JDBC exceptions and translating them to the generic, more informative exception hierarchy defined in the org.springframework.dao package.
TransactionTemplate by the way is also not a transaction manager, it's a
Template class that simplifies programmatic transaction demarcation and transaction exception handling.
The PlatformTransactionManager (and other subclasses of AbstractPlatformTransactionManager) is a transaction manager, as in it
determines if there is an existing transaction;
applies the appropriate propagation behavior;
suspends and resumes transactions if necessary;
checks the rollback-only flag on commit;
applies the appropriate modification on rollback (actual rollback or setting rollback-only);
triggers registered synchronization callbacks (if transaction synchronization is active).
So this class is responsible for the actual transaction handling, as opposed to the TransactionTemplate, which is to be used if you instead of declarative transaction handling you want to implement it programmetically. (see this blog, though quite outdated, you will see the difference between declarative and manual)
Quotes from Spring 3 Reference.
Note: Throughout the Spring Framework you will find other *Template classes as well: HibernateTemplate, JmsTemplate etc. They all follow the same pattern: template classes which radically reduce the amount of code you need to write, because all the so-called boilerplate code will be handled by them. Example (from here):
Without JdbcTemplate:
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insert(Customer customer){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
Connection conn = null;
try {
conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, customer.getCustId());
ps.setString(2, customer.getName());
ps.setInt(3, customer.getAge());
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
}
}
And with JdbcTemplate:
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insert(Customer customer){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update(sql, new Object[] { customer.getCustId(),
customer.getName(),customer.getAge()
});
}

Resources