I am having great difficulty in piecing all the information together. So let me explain as much detail as I can.
Working on a spring-jdbc, spring-transaction, MySQL db application, I want to be able to lock rows of data from a table while selecting for update. Reason being, I have business logic that transforms data at different stages. So if the application instance (on server-1) picks up record for processing, then it should not be picked up by another instance. This is somewhat similar to this question. But the answer is not acceptable to me for the same reasons as the OP had for that question.
So after carefully examining the spring-transaction reference doc and learning about how to configure transaction management with jdbcTemplate, my spring application set up looks as follows (only relevant part):
applicationContext.xml
...
...
<context:annotation-config />
...
<context:component-scan base-package="org.foo.bar"/>
...
<!-- Enable Annotation based Declarative Transaction Management -->
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
...
<bean id="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://dbserver:3306" />
<property name="username" value="foo" />
<property name="password" value="baz" />
<property name="initialSize" value="10" />
<property name="maxTotal" value="20"/>
</bean>
Service Class with target method: TransactionSvcImpl.java
public class TransactionSvcImpl implements TransactionSvc {
@Autowired
DatabaseAccessService dbAccessService;
@Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW)
@Override
public List<Foo> getFooForUpdate() {
// Below call executes a SQL like - "SELECT * FROM some_t WHERE id = 1 FOR UPDATE"
List<foo> foos = dbAccessService.getSomeRecord();
dbAccessService.updateTheRecord(foos, Status.PROCESSING);
return foos;
}
}
Now, I tested how isolation level works in MySQL by following what is described in this answer. However to make "select for update" work I had to change the following properties on the database server itself:
SET tx_isolation = 'SERIALIZABLE';
SET AUTOCOMMIT=0;
Without changing these settings, I could not get SELECT ... FOR UPDATE
to work.
Once I tested it manually, I wanted to see if this has any effect when my application code runs. So while I was keeping one of mysql commandline session active, I started my application and put a breakpoint at the dbAccessService.updateTheRecord();
line. Then in the other session, I tried to select this row (no select for update
, just a simple select
) statement. Now I could see that the row was locked because of a previous transaction that was started by my application code.
Questions:
- To achieve the row locking (
SELECT ... FOR UPDATE
behavior) through spring-transaction do I have to change settings on the database server? Like the isolation_level and autocommit? - Lets say MySQL server is running under defaults (ISOLATION_LEVEL = REPEATABLE-READS, AUTOCOMMIT=1). Now will this still work? Meaning, if I configure transaction in spring on target method as I have done above, will a row being read by one instance of application stay locked so that any other instance trying to read that row will be blocked?
- How does the configuration that we add on the
@Transactional
annotation make it to (or affect) the database? I know we can set transaction characteristics for different scopes as described in table 13.9 of the MySQL documentation here. Basically below is my imagination or guess about how all this works. Is it correct?
How it all works?
- The class/method annotated with @Transactional will result in a proxy object being created so that all the transactional stuff is added along with businiess logic the user implemented.
- The database connection is acquired and a database session is created.
- For this session, based on the configuration provided in
@Transactional
, appropriate isolation level is set (don't know how autocommit will be affected) Once the transaction is completed, session closes. Any new sessions created will set the isolation characteristics accordingly.
- Is there a better way for me to achieve the desired
SELECT ... FOR UPDATE
functionality with spring-jdbc, spring-tx? I could probably introduce my own locking mechanism like, introduce a new boolean column likeselected_for_processing
and instead of selecting, update the records to be selcted by setting theselected_for_processing
column. In the next step I would select based on this flag and another status criteria. So this way a record will only be processed once. But I want to know if there is a conventional or configuration way to achieve this? I cannot use JPA or any spring-data-x libraries. spring-data- libraries work with springframework 5.x.x or above, but I am stuck with springframework 4.2.3, hence spring-jdbc, spring-tx.
- Is there a better way for me to achieve the desired