From 697108cc42a7dbcbeaa6c405f4224cd58304ac5f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 29 Apr 2021 15:37:40 +0200 Subject: [PATCH] Support safe-updates mode in MySQLMaxValueIncrementer Prior to this commit, MySQLMaxValueIncrementer could not be used when the MySQL database was configured to use safe-updates mode. See https://dev.mysql.com/doc/refman/8.0/en/mysql-tips.html#safe-updates This commit introduces a `limit 1` clause to the generated update statement to allow MySQLMaxValueIncrementer to be compatible with MySQL safe-updates mode. Closes gh-26858 --- .../incrementer/MySQLMaxValueIncrementer.java | 15 ++++++++----- .../DataFieldMaxValueIncrementerTests.java | 22 +++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) 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 cf6d0f0414..bc00b8d925 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-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -40,22 +40,27 @@ import org.springframework.jdbc.support.JdbcUtils; * *

Example: * - *

create table tab (id int unsigned not null primary key, text varchar(100));
+ * 
+ * create table tab (id int unsigned not null primary key, text varchar(100));
  * 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 + *

If {@code cacheSize} is set, the intermediate values are served without querying the * database. If the server or your application is stopped or crashes or a transaction * is rolled back, the unused values will never be served. The maximum hole size in - * numbering is consequently the value of cacheSize. + * numbering is consequently the value of {@code 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. * + *

As of Spring Framework 5.3.7, {@code MySQLMaxValueIncrementer} is compatible with + * MySQL safe updates mode. + * * @author Jean-Pierre Pawlak * @author Thomas Risberg * @author Juergen Hoeller + * @author Sam Brannen */ public class MySQLMaxValueIncrementer extends AbstractColumnMaxValueIncrementer { @@ -141,7 +146,7 @@ public class MySQLMaxValueIncrementer extends AbstractColumnMaxValueIncrementer String columnName = getColumnName(); try { stmt.executeUpdate("update " + getIncrementerName() + " set " + columnName + - " = last_insert_id(" + columnName + " + " + getCacheSize() + ")"); + " = last_insert_id(" + columnName + " + " + getCacheSize() + ") limit 1"); } catch (SQLException ex) { throw new DataAccessResourceFailureException("Could not increment " + columnName + " for " + diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/DataFieldMaxValueIncrementerTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/DataFieldMaxValueIncrementerTests.java index d2e3594abe..7cbb99047b 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/DataFieldMaxValueIncrementerTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/DataFieldMaxValueIncrementerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -25,6 +25,7 @@ import javax.sql.DataSource; import org.junit.jupiter.api.Test; +import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; import org.springframework.jdbc.support.incrementer.HanaSequenceMaxValueIncrementer; import org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer; import org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementer; @@ -38,10 +39,13 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** + * Unit tests for {@link DataFieldMaxValueIncrementer} implementations. + * * @author Juergen Hoeller + * @author Sam Brannen * @since 27.02.2004 */ -public class DataFieldMaxValueIncrementerTests { +class DataFieldMaxValueIncrementerTests { private final DataSource dataSource = mock(DataSource.class); @@ -53,7 +57,7 @@ public class DataFieldMaxValueIncrementerTests { @Test - public void testHanaSequenceMaxValueIncrementer() throws SQLException { + void hanaSequenceMaxValueIncrementer() throws SQLException { given(dataSource.getConnection()).willReturn(connection); given(connection.createStatement()).willReturn(statement); given(statement.executeQuery("select myseq.nextval from dummy")).willReturn(resultSet); @@ -75,7 +79,7 @@ public class DataFieldMaxValueIncrementerTests { } @Test - public void testHsqlMaxValueIncrementer() throws SQLException { + void hsqlMaxValueIncrementer() throws SQLException { given(dataSource.getConnection()).willReturn(connection); given(connection.createStatement()).willReturn(statement); given(statement.executeQuery("select max(identity()) from myseq")).willReturn(resultSet); @@ -105,7 +109,7 @@ public class DataFieldMaxValueIncrementerTests { } @Test - public void testHsqlMaxValueIncrementerWithDeleteSpecificValues() throws SQLException { + void hsqlMaxValueIncrementerWithDeleteSpecificValues() throws SQLException { given(dataSource.getConnection()).willReturn(connection); given(connection.createStatement()).willReturn(statement); given(statement.executeQuery("select max(identity()) from myseq")).willReturn(resultSet); @@ -136,7 +140,7 @@ public class DataFieldMaxValueIncrementerTests { } @Test - public void testMySQLMaxValueIncrementer() throws SQLException { + void mySQLMaxValueIncrementer() throws SQLException { given(dataSource.getConnection()).willReturn(connection); given(connection.createStatement()).willReturn(statement); given(statement.executeQuery("select last_insert_id()")).willReturn(resultSet); @@ -156,14 +160,14 @@ public class DataFieldMaxValueIncrementerTests { assertThat(incrementer.nextStringValue()).isEqualTo("3"); assertThat(incrementer.nextLongValue()).isEqualTo(4); - verify(statement, times(2)).executeUpdate("update myseq set seq = last_insert_id(seq + 2)"); + verify(statement, times(2)).executeUpdate("update myseq set seq = last_insert_id(seq + 2) limit 1"); verify(resultSet, times(2)).close(); verify(statement, times(2)).close(); verify(connection, times(2)).close(); } @Test - public void testOracleSequenceMaxValueIncrementer() throws SQLException { + void oracleSequenceMaxValueIncrementer() throws SQLException { given(dataSource.getConnection()).willReturn(connection); given(connection.createStatement()).willReturn(statement); given(statement.executeQuery("select myseq.nextval from dual")).willReturn(resultSet); @@ -185,7 +189,7 @@ public class DataFieldMaxValueIncrementerTests { } @Test - public void testPostgresSequenceMaxValueIncrementer() throws SQLException { + void postgresSequenceMaxValueIncrementer() throws SQLException { given(dataSource.getConnection()).willReturn(connection); given(connection.createStatement()).willReturn(statement); given(statement.executeQuery("select nextval('myseq')")).willReturn(resultSet);