Skip to content

Commit 5c7aee7

Browse files
committed
Updating MySQLMaxValueIncrementer to not rely on MYISAM
We should no longer rely on MYISAM for the sequence table since this engine might not always be available. After this change the storage engine used by the sequence table can be MYISAM or INNODB since the sequences are allocated using a new connection without being affected by any other transactions that might be in progress. To allow users to fall back on the original functionality of using MYISAM tables for the incrementer, we add a `useNewConnection` flag to indicate whether or not to use a new connection for the incrementer. This flag defaults to true. Issue: SPR-15107
1 parent b7471e7 commit 5c7aee7

File tree

1 file changed

+91
-13
lines changed

1 file changed

+91
-13
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/MySQLMaxValueIncrementer.java

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,20 +33,24 @@
3333
* key column should <i>NOT</i> be auto-increment, as the sequence table does the job.
3434
*
3535
* <p>The sequence is kept in a table; there should be one sequence table per
36-
* table that needs an auto-generated key. The table type of the sequence table
37-
* should be MyISAM so the sequences are allocated without regard to any
38-
* transactions that might be in progress.
36+
* table that needs an auto-generated key. The storage engine used by the sequence table
37+
* can be MYISAM or INNODB since the sequences are allocated using a separate connection
38+
* without being affected by any other transactions that might be in progress.
3939
*
4040
* <p>Example:
4141
*
4242
* <pre class="code">create table tab (id int unsigned not null primary key, text varchar(100));
43-
* create table tab_sequence (value int not null) type=MYISAM;
43+
* create table tab_sequence (value int not null);
4444
* insert into tab_sequence values(0);</pre>
4545
*
4646
* If "cacheSize" is set, the intermediate values are served without querying the
4747
* database. If the server or your application is stopped or crashes or a transaction
4848
* is rolled back, the unused values will never be served. The maximum hole size in
4949
* numbering is consequently the value of cacheSize.
50+
*
51+
* <p>It is possible to avoid acquiring a new connection for the incrementer by setting the
52+
* "useNewConnection" property to false. In this case you <i>MUST</i> use a non-transactional
53+
* storage engine like MYISAM when defining the incrementer table.
5054
*
5155
* @author Jean-Pierre Pawlak
5256
* @author Thomas Risberg
@@ -63,6 +67,14 @@ public class MySQLMaxValueIncrementer extends AbstractColumnMaxValueIncrementer
6367
/** The max id to serve */
6468
private long maxId = 0;
6569

70+
/**
71+
* Whether or not to use a new connection for the incrementer. Defaults to true
72+
* in order to support transactional storage engines. Set this to false if the storage engine
73+
* for the incrementer table is non-transactional like MYISAM and you prefer to not acquire
74+
* an additional database connection
75+
*/
76+
private boolean useNewConnection = true;
77+
6678

6779
/**
6880
* Default constructor for bean property style usage.
@@ -83,24 +95,73 @@ public MySQLMaxValueIncrementer(DataSource dataSource, String incrementerName, S
8395
super(dataSource, incrementerName, columnName);
8496
}
8597

98+
/**
99+
* Convenience constructor for setting whether to use a new connection for the incrementer.
100+
* @param dataSource the DataSource to use
101+
* @param incrementerName the name of the sequence/table to use
102+
* @param columnName the name of the column in the sequence table to use
103+
* @param useNewConnection whether to use a new connection for the incrementer
104+
*/
105+
public MySQLMaxValueIncrementer(DataSource dataSource, String incrementerName, String columnName,
106+
boolean useNewConnection) {
107+
super(dataSource, incrementerName, columnName);
108+
this.useNewConnection = useNewConnection;
109+
}
110+
111+
112+
/**
113+
* Return whether to use a new connection for the incrementer.
114+
*/
115+
public boolean isUseNewConnection() {
116+
return useNewConnection;
117+
}
118+
119+
/**
120+
* Set whether to use a new connection for the incrementer.
121+
*/
122+
public void setUseNewConnection(boolean useNewConnection) {
123+
this.useNewConnection = useNewConnection;
124+
}
86125

87126
@Override
88127
protected synchronized long getNextKey() throws DataAccessException {
89128
if (this.maxId == this.nextId) {
90129
/*
91-
* Need to use straight JDBC code because we need to make sure that the insert and select
92-
* are performed on the same connection (otherwise we can't be sure that last_insert_id()
93-
* returned the correct value)
130+
* If useNewConnection is true, then we obtain a non-managed connection so our modifications
131+
* are handled in a separate transaction. If it is false, then we use the current transaction's
132+
* connection relying on the use of a non-transactional storage engine like MYISAM for the
133+
* incrementer table. We also use straight JDBC code because we need to make sure that the insert
134+
* and select are performed on the same connection (otherwise we can't be sure that last_insert_id()
135+
* returned the correct value).
94136
*/
95-
Connection con = DataSourceUtils.getConnection(getDataSource());
137+
Connection con = null;
96138
Statement stmt = null;
139+
boolean mustRestoreAutoCommit = false;
97140
try {
141+
if (useNewConnection) {
142+
con = getDataSource().getConnection();
143+
if (con.getAutoCommit()) {
144+
mustRestoreAutoCommit = true;
145+
con.setAutoCommit(false);
146+
}
147+
}
148+
else {
149+
con = DataSourceUtils.getConnection(getDataSource());
150+
}
98151
stmt = con.createStatement();
99-
DataSourceUtils.applyTransactionTimeout(stmt, getDataSource());
152+
if (!useNewConnection) {
153+
DataSourceUtils.applyTransactionTimeout(stmt, getDataSource());
154+
}
100155
// Increment the sequence column...
101156
String columnName = getColumnName();
102-
stmt.executeUpdate("update "+ getIncrementerName() + " set " + columnName +
103-
" = last_insert_id(" + columnName + " + " + getCacheSize() + ")");
157+
try {
158+
stmt.executeUpdate("update " + getIncrementerName() + " set " + columnName +
159+
" = last_insert_id(" + columnName + " + " + getCacheSize() + ")");
160+
}
161+
catch (SQLException ex) {
162+
throw new DataAccessResourceFailureException("Could not increment " + columnName + " for " +
163+
getIncrementerName() + " sequence table", ex);
164+
}
104165
// Retrieve the new max of the sequence column...
105166
ResultSet rs = stmt.executeQuery(VALUE_SQL);
106167
try {
@@ -119,7 +180,24 @@ protected synchronized long getNextKey() throws DataAccessException {
119180
}
120181
finally {
121182
JdbcUtils.closeStatement(stmt);
122-
DataSourceUtils.releaseConnection(con, getDataSource());
183+
if (useNewConnection) {
184+
try {
185+
con.commit();
186+
if (mustRestoreAutoCommit) {
187+
con.setAutoCommit(true);
188+
}
189+
}
190+
catch (SQLException ignore) {
191+
throw new DataAccessResourceFailureException(
192+
"Unable to commit new sequence value changes for " + getIncrementerName());
193+
}
194+
try {
195+
con.close();
196+
} catch (SQLException ignore) {}
197+
}
198+
else {
199+
DataSourceUtils.releaseConnection(con, getDataSource());
200+
}
123201
}
124202
}
125203
else {

0 commit comments

Comments
 (0)