diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/MySQLMaxValueIncrementer.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/MySQLMaxValueIncrementer.java index 2c3ad290a20..da5f604b8b1 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/MySQLMaxValueIncrementer.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/incrementer/MySQLMaxValueIncrementer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,14 +33,14 @@ import org.springframework.jdbc.support.JdbcUtils; * key column should NOT be auto-increment, as the sequence table does the job. * *
The sequence is kept in a table; there should be one sequence table per - * table that needs an auto-generated key. The table type of the sequence table - * should be MyISAM so the sequences are allocated without regard to any - * transactions that might be in progress. + * table that needs an auto-generated key. The storage engine used by the sequence table + * can be MYISAM or INNODB since the sequences are allocated using a separate connection + * without being affected by any other transactions that might be in progress. * *
Example: * *
create table tab (id int unsigned not null primary key, text varchar(100)); - * create table tab_sequence (value int not null) type=MYISAM; + * create table tab_sequence (value int not null); * insert into tab_sequence values(0);* * If "cacheSize" is set, the intermediate values are served without querying the @@ -48,6 +48,10 @@ import org.springframework.jdbc.support.JdbcUtils; * is rolled back, the unused values will never be served. The maximum hole size in * numbering is consequently the value of cacheSize. * + *
It is possible to avoid acquiring a new connection for the incrementer by setting the + * "useNewConnection" property to false. In this case you MUST use a non-transactional + * storage engine like MYISAM when defining the incrementer table. + * * @author Jean-Pierre Pawlak * @author Thomas Risberg * @author Juergen Hoeller @@ -63,6 +67,9 @@ public class MySQLMaxValueIncrementer extends AbstractColumnMaxValueIncrementer /** The max id to serve */ private long maxId = 0; + /** Whether or not to use a new connection for the incrementer */ + private boolean useNewConnection = false; + /** * Default constructor for bean property style usage. @@ -76,7 +83,7 @@ public class MySQLMaxValueIncrementer extends AbstractColumnMaxValueIncrementer /** * Convenience constructor. * @param dataSource the DataSource to use - * @param incrementerName the name of the sequence/table to use + * @param incrementerName the name of the sequence table to use * @param columnName the name of the column in the sequence table to use */ public MySQLMaxValueIncrementer(DataSource dataSource, String incrementerName, String columnName) { @@ -84,23 +91,61 @@ public class MySQLMaxValueIncrementer extends AbstractColumnMaxValueIncrementer } + /** + * Set whether to use a new connection for the incrementer. + *
{@code true} is necessary to support transactional storage engines, + * using an isolated separate transaction for the increment operation. + * {@code false} is sufficient if the storage engine of the sequence table + * is non-transactional (like MYISAM), avoiding the effort of acquiring an + * extra {@code Connection} for the increment operation. + *
Default is {@code false} in the Spring Framework 4.3.x line. + * @since 4.3.6 + * @see DataSource#getConnection() + */ + public void setUseNewConnection(boolean useNewConnection) { + this.useNewConnection = useNewConnection; + } + + @Override protected synchronized long getNextKey() throws DataAccessException { if (this.maxId == this.nextId) { /* - * Need to use straight JDBC code because we need to make sure that the insert and select - * are performed on the same connection (otherwise we can't be sure that last_insert_id() - * returned the correct value) + * If useNewConnection is true, then we obtain a non-managed connection so our modifications + * are handled in a separate transaction. If it is false, then we use the current transaction's + * connection relying on the use of a non-transactional storage engine like MYISAM for the + * incrementer table. We also use straight JDBC code because we need to make sure that the insert + * and select are performed on the same connection (otherwise we can't be sure that last_insert_id() + * returned the correct value). */ - Connection con = DataSourceUtils.getConnection(getDataSource()); + Connection con = null; Statement stmt = null; + boolean mustRestoreAutoCommit = false; try { + if (this.useNewConnection) { + con = getDataSource().getConnection(); + if (con.getAutoCommit()) { + mustRestoreAutoCommit = true; + con.setAutoCommit(false); + } + } + else { + con = DataSourceUtils.getConnection(getDataSource()); + } stmt = con.createStatement(); - DataSourceUtils.applyTransactionTimeout(stmt, getDataSource()); + if (!this.useNewConnection) { + DataSourceUtils.applyTransactionTimeout(stmt, getDataSource()); + } // Increment the sequence column... String columnName = getColumnName(); - stmt.executeUpdate("update "+ getIncrementerName() + " set " + columnName + - " = last_insert_id(" + columnName + " + " + getCacheSize() + ")"); + try { + stmt.executeUpdate("update " + getIncrementerName() + " set " + columnName + + " = last_insert_id(" + columnName + " + " + getCacheSize() + ")"); + } + catch (SQLException ex) { + throw new DataAccessResourceFailureException("Could not increment " + columnName + " for " + + getIncrementerName() + " sequence table", ex); + } // Retrieve the new max of the sequence column... ResultSet rs = stmt.executeQuery(VALUE_SQL); try { @@ -119,7 +164,24 @@ public class MySQLMaxValueIncrementer extends AbstractColumnMaxValueIncrementer } finally { JdbcUtils.closeStatement(stmt); - DataSourceUtils.releaseConnection(con, getDataSource()); + if (con != null) { + if (this.useNewConnection) { + try { + con.commit(); + if (mustRestoreAutoCommit) { + con.setAutoCommit(true); + } + } + catch (SQLException ignore) { + throw new DataAccessResourceFailureException( + "Unable to commit new sequence value changes for " + getIncrementerName()); + } + JdbcUtils.closeConnection(con); + } + else { + DataSourceUtils.releaseConnection(con, getDataSource()); + } + } } } else {