Can you explain why #Transactional not rolling back - java

I am trying to create two tables(ABC, XYZ) in Database. XYZ syntax intentionally has wrong syntax and is suppose to fail. The idea is that table creation of ABC should be rolled back when XYZ is tried. I am new to Spring Transaction and have searched around to come up with this simple example the logs seem to show that TransactionManager is rolling back but at the end I see that the table ABC is present in Database. Can anybody explain why #Transactional is not rolling back here...
package com.tango.db.dao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
public class DBDAO {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
#Transactional(rollbackFor = Exception.class)
public void createTables(){
jdbcTemplate.execute("CREATE TABLE ABC(ID NUMBER NOT NULL, NAME VARCHAR2(35), primary key(ID))");
jdbcTemplate.execute("CREATE TABLE XYZ(ID NUMBER NOT NULL, NAME VARCHAR2(35), UPDATE DATE(7), primary key(ID))");
}
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
DBDAO dbdao = (DBDAO)ctx.getBean("dbDAO");
dbdao.createTables();
}
}
spring-context.xml
<bean id="local.oracle.dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:#10.211.55.6:1521:xe"/>
<property name="username" value="abc"/>
<property name="password" value="1234"/>
<property name="defaultAutoCommit" value="false"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="local.oracle.dataSource"/>
</bean>
<bean id="dbDAO" class="com.tango.db.dao.DBDAO">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="local.oracle.dataSource"/>
</bean>
Following are the logs:
DEBUG SQLErrorCodeSQLExceptionTranslator - Translating SQLException with SQL state '42000', error code '904', message [ORA-00904: : invalid identifier
]; SQL was [CREATE TABLE XYZ(ID NUMBER NOT NULL, NAME VARCHAR2(35), UPDATE DATE(7), primary key(ID))] for task [StatementCallback]
DEBUG DataSourceTransactionManager - Initiating transaction rollback
DEBUG DataSourceTransactionManager - Rolling back JDBC transaction on Connection [jdbc:oracle:thin:#10.211.55.6:1521:xe, UserName=DBO, Oracle JDBC driver]
DEBUG DataSourceTransactionManager - Releasing JDBC Connection [jdbc:oracle:thin:#10.211.55.6:1521:xe, UserName=DBO, Oracle JDBC driver] after transaction
DEBUG DataSourceUtils - Returning JDBC Connection to DataSource
Exception in thread "main" org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [CREATE TABLE XYZ(ID NUMBER NOT NULL, NAME VARCHAR2(35), UPDATE DATE(7), primary key(ID))]; nested exception is java.sql.SQLSyntaxErrorException: ORA-00904: : invalid identifier
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:233)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:407)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:428)
at com.tango.db.dao.DBDAO.createTables(DBDAO.java:19)
at com.tango.db.dao.DBDAO$$FastClassByCGLIB$$112527a.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:689)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
at com.tango.db.dao.DBDAO$$EnhancerByCGLIB$$7b0ac5f7.createTables(<generated>)
at com.tango.db.dao.DBDAO.main(DBDAO.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.sql.SQLSyntaxErrorException: ORA-00904: : invalid identifier
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:440)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:837)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:445)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:191)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:523)
at oracle.jdbc.driver.T4CStatement.doOall8(T4CStatement.java:193)
at oracle.jdbc.driver.T4CStatement.executeForRows(T4CStatement.java:999)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1315)
at oracle.jdbc.driver.OracleStatement.executeInternal(OracleStatement.java:1890)
at oracle.jdbc.driver.OracleStatement.execute(OracleStatement.java:1855)
at oracle.jdbc.driver.OracleStatementWrapper.execute(OracleStatementWrapper.java:304)
at org.apache.commons.dbcp.DelegatingStatement.execute(DelegatingStatement.java:264)
at org.apache.commons.dbcp.DelegatingStatement.execute(DelegatingStatement.java:264)
at org.springframework.jdbc.core.JdbcTemplate$1ExecuteStatementCallback.doInStatement(JdbcTemplate.java:421)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:396)
... 16 more

In your case the transaction boundary is not entirely up to Spring. If you dig a bit deeper into Oracle documentation, it defines the transaction boundary as follow:
A transaction begins with the first executable SQL statement. A
transaction ends when it is committed or rolled back, either
explicitly with a COMMIT or ROLLBACK statement or implicitly when a
DDL statement is issued.
http://docs.oracle.com/cd/B28359_01/server.111/b28318/transact.htm#i6564
CREATE TABLE is a DDL (Data Definition Language) statement. Hence when you issued the first CREATE TABLE, Oracle took it as an implicit end-of-transaction. Therefore when your second CREATE TABLE is issued and failed, it's too late to revert the first one.

This is because a create table is a DDL (data definition language) statement and only DML (data modification language) statements are rolledback. There is an implicit commit at every DDL statement, so they cannot be rolledback.
To be complete, DML statements are: insert, update and delete.

Looks like you're using JDK proxies but do not have any interface declared for Spring to use to create a proxy against. DBDAO needs to be an interface that has the createTables method on it, so Spring has something to create the runtime implementation of that includes the transaction code.

Related

How to access H2 table in Spring #PostConstruct?

I have defined the H2 database as unit test source database.
<jdbc:embedded-database id="adminmaster" type="H2">
<jdbc:script location="classpath:db/schemas.sql" encoding="UTF-8"/>
<jdbc:script location="classpath:db/test_data.sql" encoding="UTF-8"/>
</jdbc:embedded-database>
And config the datasource connection pool
<bean id="adminDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:mem:adminmaster;MODE=MYSQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1"/>
<property name='username' value='sa'/>
<property name='password' value=''/>
</bean>
But the problem is that when I want to query some data in table XXX, I got:
Cause: org.h2.jdbc.JdbcSQLException: Table "XXX" not
found; SQL statement:
#PostConstruct
public void init() {
try {
// here I tried to get data from database
} catch (Exception e) {
log.error("load db jar failed, error: {}", Throwables.getStackTraceAsString(e));
}
I found I can query data in the normal #Test methods, But when I tried to run Spring Test, the #PostConstruct method will throw an exception.
If you are trying to access data from your test database, Junit 4 has the annotations #Before (before each test) and #BeforeClass (only once)
Junit5 has more explicit annotations :
#BeforeEach and #BeforeAll
#PostConstruct only ensures that your bean is correctly initialized with all the dependencies. Not that the DB is ready for use ;)
If you are using another test library look at the equivalence. But if you are using Spring you are certainly using Junit.
https://www.baeldung.com/junit-before-beforeclass-beforeeach-beforeall

Call stored procedure on mysql slave using ReplicationDriver

I have a Spring application that currently executes some queries utilizing stored procedures. The configuration is something like this:
Datasource:
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.ReplicationDriver"/>
<property name="url" value="jdbc:mysql:replication://master,slave1,slave2/db?allowMultiQueries=true"/>
<property name="username" value="${db.dbusername}"/>
<property name="password" value="${db.dbpassword}"/>
<property name="defaultReadOnly" value="true"/>
</bean>
<bean id="jdbcDeviceDAO" class="dao.jdbc.JdbcDeviceDAO">
<property name="dataSource" ref="dataSource"/>
</bean>
DAO:
public class JdbcDeviceDAO implements DeviceDAO {
// ...
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.procGetCombinedDeviceRouting = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("get_combined_device_routing");
// ...
}
public CombinedDeviceRouting getCombinedDeviceRouting(String deviceName, String deviceNameType) {
SqlParameterSource in = createParameters(deviceName, deviceNameType);
Map<String, Object> results = this.procGetCombinedDeviceRouting.execute(in);
return extractResults(results);
}
Now when I call getCombinedDeviceRouting(...) it fails with the following exception:
org.springframework.dao.TransientDataAccessResourceException: CallableStatementCallback; SQL [{call get_combined_device_routing()}]; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
I know the connection is read-only and I need it to be that way so the queries are load-balanced between slave hosts. But the stored procedure is actually read only, it's just a lot of SELECT statements, in fact I tried adding READS SQL DATA to its definition but it didn't work.
Finally I came to the point of reading the mysql's connector code and I found this:
protected boolean checkReadOnlySafeStatement() throws SQLException {
synchronized (checkClosed().getConnectionMutex()) {
return this.firstCharOfStmt == 'S' || !this.connection.isReadOnly();
}
}
It sounds naive, but is the connector checking whether my statement is read-only by just matching the first character with 'S'?
If this is the case, it seems like there's no way of calling a stored procedure on a slave host, because the statement starts with 'C' (CALL ...).
Does anyone know if there's a workaround for this problem? Or maybe I'm wrong assuming this first character check?
It appears as though this is a bug with the driver I had a look at the code to see if there is an easy extension point, but it looks like you'd have to extend a lot of classes to affect this behaviour :(

hibernate transaction not rolling back correctly

I have 2 tables, say Item and Property and a hibernate object mapped to both. The mapping for table Item to Property looks like
<set name="propertySet" cascade="all-delete-orphan">
<key column="item_id" not-null="true"/>
<one-to-many class="Property"/>
</set>
An item can have multiple properties. Everything like select, insert works properly. but when there is an error, the inserts to the property table do not rollback.
What happens is that if i am editing an item with N properties and enter an invalid value in a field, the next time I retrieve the item, it has 2*N properties.
Edit ---
What my class looks like is
#Autowired
SessionFactory sessionFactory
#Transactional
public void updateItem(Item i){
...
// The only 2 statements dealing with hibernate or session in this function
ItemModel im = sessionFactory.getCurrentSession().get(...);
sessionFactory.getCurrentSession().update(updatedItem);
...
}
I am using annotated transactions (#Transactional) with the spring framework and the lowest exception getting thrown is
Caused by: org.springframework.transaction.TransactionSystemException: Could not roll back Hibernate transaction; nested exception is org.hibernate.Tra
nsactionException: Transaction not successfully started
at org.springframework.orm.hibernate3.HibernateTransactionManager.doRollback(HibernateTransactionManager.java:679)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:845)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:822)
at org.springframework.transaction.interceptor.TransactionAspectSupport.completeTransactionAfterThrowing(TransactionAspectSupport.java:412)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:111)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:625)
Caused by: org.hibernate.TransactionException: Transaction not successfully started
at org.hibernate.transaction.JDBCTransaction.rollback(JDBCTransaction.java:183)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doRollback(HibernateTransactionManager.java:676)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:845)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:822)
at org.springframework.transaction.interceptor.TransactionAspectSupport.completeTransactionAfterThrowing(TransactionAspectSupport.java:412)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:111)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:625)
...
...
at org.apache.catalina.valves.SSLValve.invoke(SSLValve.java:113)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286)
at org.apache.coyote.http11.Http11NioProcessor.process(Http11NioProcessor.java:894)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:719)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:2101)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Transactions don't "cascade". At the JDBC level, a transaction consists of:
Turning off autocommit
Executing some statements
Calling java.sql.Connection.commit() or java.sql.Connection.rollback().
If you're saying that some things are being committed and some are rolled back, then there's something wrong in your transaction management. Either autocommit is on or you actually have multiple calls to commit() happening.
Transactions are managed under the hood by Spring if you do the following: in your XML config, you need to 1) enable transactions, and 2) configure a transaction manager, as follows:
<tx:annotation-driven />
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mainSessionFactory" />
</bean>
Tx schemaLocation is http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"

Translating PersistenceException to DataAccessException in Spring

I'm trying to handle unique key constraint violations in a Spring + JPA + Hibernate environment.
I use PersistenceExceptionTranslationPostProcessor to translate a PersistenceException to a DataAccessException. When there's a unique key constraint violation, I'd expect a DuplicateKeyException or a DataIntegrityViolationException thrown, but all I get is a JpaSystemException that wraps a PersistenceException.
Isn't the whole point of using the DataAccessException hierarchy that it's fine-grained enough not to have to look up the vendor-specific error code?
How do I have Spring translate a PersistenceException to a more specific DataAccessException ?
EDIT: I noticed that this.jpaDialect in DataAccessUtils.translateIfNecessary() is null. Is there some setting I need to configure to set this.jpaDialect to HibernateJpaDialect?
Thanks!
Apparently you don't have jpaDialect set. For Hibernate it should look like this:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
<!-- ... -->
</bean>

How to set a default query timeout with JPA and Hibernate?

I am doing some big queries on my database with Hibernate and I sometimes hit timeouts. I would like to avoid setting the timeout manually on every Query or Criteria.
Is there any property I can give to my Hibernate configuration that would set an acceptable default for all queries I run?
If not, how can I set a default timeout value on Hibernate queries?
JPA 2 defines the javax.persistence.query.timeout hint to specify default timeout in milliseconds. Hibernate 3.5 (currently still in beta) will support this hint.
See also https://hibernate.atlassian.net/browse/HHH-4662
JDBC has this mechanism named Query Timeout, you can invoke setQueryTime method of java.sql.Statement object to enable this setting.
Hibernate cannot do this in unified way.
If your application retrive JDBC connection vi java.sql.DataSource, the question can be resolved easily.
we can create a DateSourceWrapper to proxy Connnection which do setQueryTimeout for every Statement it created.
The example code is easy to read, I use some spring util classes to help this.
public class QueryTimeoutConfiguredDataSource extends DelegatingDataSource {
private int queryTimeout;
public QueryTimeoutConfiguredDataSource(DataSource dataSource) {
super(dataSource);
}
// override this method to proxy created connection
#Override
public Connection getConnection() throws SQLException {
return proxyWithQueryTimeout(super.getConnection());
}
// override this method to proxy created connection
#Override
public Connection getConnection(String username, String password) throws SQLException {
return proxyWithQueryTimeout(super.getConnection(username, password));
}
private Connection proxyWithQueryTimeout(final Connection connection) {
return proxy(connection, new InvocationHandler() {
//All the Statement instances are created here, we can do something
//If the return is instance of Statement object, we set query timeout to it
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = method.invoke(connection, args);
if (object instanceof Statement) {
((Statement) object).setQueryTimeout(queryTimeout);
}
return object;
});
}
private Connection proxy(Connection connection, InvocationHandler invocationHandler) {
return (Connection) Proxy.newProxyInstance(
connection.getClass().getClassLoader(),
ClassUtils.getAllInterfaces(connection),
invocationHandler);
}
public void setQueryTimeout(int queryTimeout) {
this.queryTimeout = queryTimeout;
}
}
Now we can use this QueryTimeoutConfiguredDataSource to wrapper your exists DataSource to set Query Timeout for every Statement transparently!
Spring config file:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource">
<bean class="com.stackoverflow.QueryTimeoutConfiguredDataSource">
<constructor-arg ref="dataSource"/>
<property name="queryTimeout" value="1" />
</bean>
</property>
</bean>
Here are a few ways:
Use a factory or base class method to create all queries and set the timeout before returning the Query object
Create your own version of org.hibernate.loader.Loader and set the timeout in doQuery
Use AOP, e.g. Spring, to return a proxy for Session; add advice to it that wraps the createQuery method and sets the timeout on the Query object before returning it
Yes, you can do that.
As I explained in this article, all you need to do is to pass the JPA query hint as a global property:
<property
name="javax.persistence.query.timeout"
value="1000"
/>
Now, when executing a JPQL query that will timeout after 1 second:
List<Post> posts = entityManager
.createQuery(
"select p " +
"from Post p " +
"where function('1 >= ALL ( SELECT 1 FROM pg_locks, pg_sleep(2) ) --',) is ''", Post.class)
.getResultList();
Hibernate will throw a query timeout exception:
SELECT p.id AS id1_0_,
p.title AS title2_0_
FROM post p
WHERE 1 >= ALL (
SELECT 1
FROM pg_locks, pg_sleep(2)
) --()=''
-- SQL Error: 0, SQLState: 57014
-- ERROR: canceling statement due to user request
For more details about setting a timeout interval for Hibernate queries, check out this article.
For setting global timeout values at query level - Add the below to config file.
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
<property name="queryTimeout" value="60"></property>
</bean>
For setting global timeout values at transaction(INSERT/UPDATE) level - Add the below to config file.
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="myEmf" />
<property name="dataSource" ref="dataSource" />
<property name="defaultTimeout" value="60" />
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
</property>
</bean>

Categories