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.
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.
5050 *
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.
54+ *
5155 * @author Jean-Pierre Pawlak
5256 * @author Thomas Risberg
5357 * @author Juergen Hoeller
@@ -63,6 +67,9 @@ public class MySQLMaxValueIncrementer extends AbstractColumnMaxValueIncrementer
6367 /** The max id to serve */
6468 private long maxId = 0 ;
6569
70+ /** Whether or not to use a new connection for the incrementer */
71+ private boolean useNewConnection = false ;
72+
6673
6774 /**
6875 * Default constructor for bean property style usage.
@@ -76,31 +83,69 @@ public MySQLMaxValueIncrementer() {
7683 /**
7784 * Convenience constructor.
7885 * @param dataSource the DataSource to use
79- * @param incrementerName the name of the sequence/ table to use
86+ * @param incrementerName the name of the sequence table to use
8087 * @param columnName the name of the column in the sequence table to use
8188 */
8289 public MySQLMaxValueIncrementer (DataSource dataSource , String incrementerName , String columnName ) {
8390 super (dataSource , incrementerName , columnName );
8491 }
8592
8693
94+ /**
95+ * Set whether to use a new connection for the incrementer.
96+ * <p>{@code true} is necessary to support transactional storage engines,
97+ * using an isolated separate transaction for the increment operation.
98+ * {@code false} is sufficient if the storage engine of the sequence table
99+ * is non-transactional (like MYISAM), avoiding the effort of acquiring an
100+ * extra {@code Connection} for the increment operation.
101+ * <p>Default is {@code false} in the Spring Framework 4.3.x line.
102+ * @since 4.3.6
103+ * @see DataSource#getConnection()
104+ */
105+ public void setUseNewConnection (boolean useNewConnection ) {
106+ this .useNewConnection = useNewConnection ;
107+ }
108+
109+
87110 @ Override
88111 protected synchronized long getNextKey () throws DataAccessException {
89112 if (this .maxId == this .nextId ) {
90113 /*
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)
114+ * If useNewConnection is true, then we obtain a non-managed connection so our modifications
115+ * are handled in a separate transaction. If it is false, then we use the current transaction's
116+ * connection relying on the use of a non-transactional storage engine like MYISAM for the
117+ * incrementer table. We also use straight JDBC code because we need to make sure that the insert
118+ * and select are performed on the same connection (otherwise we can't be sure that last_insert_id()
119+ * returned the correct value).
94120 */
95- Connection con = DataSourceUtils . getConnection ( getDataSource ()) ;
121+ Connection con = null ;
96122 Statement stmt = null ;
123+ boolean mustRestoreAutoCommit = false ;
97124 try {
125+ if (this .useNewConnection ) {
126+ con = getDataSource ().getConnection ();
127+ if (con .getAutoCommit ()) {
128+ mustRestoreAutoCommit = true ;
129+ con .setAutoCommit (false );
130+ }
131+ }
132+ else {
133+ con = DataSourceUtils .getConnection (getDataSource ());
134+ }
98135 stmt = con .createStatement ();
99- DataSourceUtils .applyTransactionTimeout (stmt , getDataSource ());
136+ if (!this .useNewConnection ) {
137+ DataSourceUtils .applyTransactionTimeout (stmt , getDataSource ());
138+ }
100139 // Increment the sequence column...
101140 String columnName = getColumnName ();
102- stmt .executeUpdate ("update " + getIncrementerName () + " set " + columnName +
103- " = last_insert_id(" + columnName + " + " + getCacheSize () + ")" );
141+ try {
142+ stmt .executeUpdate ("update " + getIncrementerName () + " set " + columnName +
143+ " = last_insert_id(" + columnName + " + " + getCacheSize () + ")" );
144+ }
145+ catch (SQLException ex ) {
146+ throw new DataAccessResourceFailureException ("Could not increment " + columnName + " for " +
147+ getIncrementerName () + " sequence table" , ex );
148+ }
104149 // Retrieve the new max of the sequence column...
105150 ResultSet rs = stmt .executeQuery (VALUE_SQL );
106151 try {
@@ -119,7 +164,24 @@ protected synchronized long getNextKey() throws DataAccessException {
119164 }
120165 finally {
121166 JdbcUtils .closeStatement (stmt );
122- DataSourceUtils .releaseConnection (con , getDataSource ());
167+ if (con != null ) {
168+ if (this .useNewConnection ) {
169+ try {
170+ con .commit ();
171+ if (mustRestoreAutoCommit ) {
172+ con .setAutoCommit (true );
173+ }
174+ }
175+ catch (SQLException ignore ) {
176+ throw new DataAccessResourceFailureException (
177+ "Unable to commit new sequence value changes for " + getIncrementerName ());
178+ }
179+ JdbcUtils .closeConnection (con );
180+ }
181+ else {
182+ DataSourceUtils .releaseConnection (con , getDataSource ());
183+ }
184+ }
123185 }
124186 }
125187 else {
0 commit comments