Skip to content

Commit 60882ce

Browse files
authored
Merge pull request #1286 from trisberg/SPR-15107
SPR-15107 Updating MySQLMaxValueIncrementer to not rely on MYISAM
2 parents 0403fe3 + 5c7aee7 commit 60882ce

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)