This is an automated email from the ASF dual-hosted git repository.
aadamchik pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/master by this push:
new a3cf68b16 CAY-2701 DST-related LocalDateTime issues
a3cf68b16 is described below
commit a3cf68b163d67352ae6661563d15a536cf38e553
Author: Andrus Adamchik <[email protected]>
AuthorDate: Thu Jan 30 17:35:37 2025 -0500
CAY-2701 DST-related LocalDateTime issues
---
RELEASE-NOTES.txt | 1 +
.../org/apache/cayenne/dba/mysql/MySQLAdapter.java | 24 +-
.../cayenne/dba/mysql/MySQLLocalDateTimeType.java | 53 ++++
.../cayenne/access/types/DateTimeTypesIT.java | 330 ++++++++++++---------
.../testcontainers/MysqlContainerProvider.java | 4 +-
pom.xml | 9 +-
6 files changed, 271 insertions(+), 150 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 21757a41c..ddeee5276 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -18,6 +18,7 @@ CAY-2873 Change Orderings.orderedList() to accept a
Collection rather than a Lis
Bug Fixes:
+CAY-2701 MySQL DST-related LocalDateTime issues
CAY-2871 QualifierTranslator breaks on a relationship with a compound FK
CAY-2872 CayenneModeler "Documentation" link is broken
diff --git
a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index 4301ee40a..3aec4a1bd 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -19,16 +19,6 @@
package org.apache.cayenne.dba.mysql;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
import org.apache.cayenne.access.DataNode;
import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
import org.apache.cayenne.access.translator.ParameterBinding;
@@ -49,7 +39,6 @@ import org.apache.cayenne.configuration.Constants;
import org.apache.cayenne.configuration.RuntimeProperties;
import org.apache.cayenne.dba.DefaultQuotingStrategy;
import org.apache.cayenne.dba.JdbcAdapter;
-import org.apache.cayenne.dba.PkGenerator;
import org.apache.cayenne.dba.QuotingStrategy;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.di.Inject;
@@ -59,6 +48,16 @@ import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.SQLAction;
import org.apache.cayenne.resource.ResourceLocator;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
/**
* DbAdapter implementation for <a href="http://www.mysql.com">MySQL RDBMS</a>.
* <h3>
@@ -149,6 +148,9 @@ public class MySQLAdapter extends JdbcAdapter {
protected void configureExtendedTypes(ExtendedTypeMap map) {
super.configureExtendedTypes(map);
+ // TODO: this may need to be made universal across adapters
+ map.registerType(new MySQLLocalDateTimeType());
+
// must handle CLOBs as strings, otherwise there
// are problems with NULL clobs that are treated
// as empty strings... somehow this doesn't happen
diff --git
a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLLocalDateTimeType.java
b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLLocalDateTimeType.java
new file mode 100644
index 000000000..a85b3db45
--- /dev/null
+++
b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLLocalDateTimeType.java
@@ -0,0 +1,53 @@
+package org.apache.cayenne.dba.mysql;
+
+import org.apache.cayenne.access.types.ExtendedType;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.time.LocalDateTime;
+
+/**
+ * An extended type for DST-safe LocalDateTime handling that bypasses timezone
resolution by reading and writing
+ * timestamps directly as LocalDateTime.
+ *
+ * @since 5.0
+ */
+// TODO: only used by MySQL now. Test compatibility on other engines, and make
it a universal approach
+class MySQLLocalDateTimeType implements ExtendedType<LocalDateTime> {
+
+ @Override
+ public String getClassName() {
+ return LocalDateTime.class.getName();
+ }
+
+ @Override
+ public LocalDateTime materializeObject(ResultSet rs, int index, int type)
throws Exception {
+ return rs.getObject(index, LocalDateTime.class);
+ }
+
+ @Override
+ public LocalDateTime materializeObject(CallableStatement rs, int index,
int type) throws Exception {
+ return rs.getObject(index, LocalDateTime.class);
+ }
+
+ @Override
+ public void setJdbcObject(
+ PreparedStatement st,
+ LocalDateTime val,
+ int pos,
+ int type,
+ int precision) throws Exception {
+
+ if (val == null) {
+ st.setNull(pos, type);
+ } else {
+ st.setObject(pos, val, type);
+ }
+ }
+
+ @Override
+ public String toString(LocalDateTime value) {
+ return String.valueOf(value);
+ }
+}
diff --git
a/cayenne/src/test/java/org/apache/cayenne/access/types/DateTimeTypesIT.java
b/cayenne/src/test/java/org/apache/cayenne/access/types/DateTimeTypesIT.java
index a1769bc00..ab9a4b210 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/types/DateTimeTypesIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/types/DateTimeTypesIT.java
@@ -22,12 +22,15 @@ package org.apache.cayenne.access.types;
import org.apache.cayenne.access.DataContext;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.query.SQLExec;
+import org.apache.cayenne.query.SQLSelect;
import org.apache.cayenne.test.jdbc.DBHelper;
import org.apache.cayenne.testdo.datetime.DurationTestEntity;
import org.apache.cayenne.testdo.datetime.LocalDateTestEntity;
import org.apache.cayenne.testdo.datetime.LocalDateTimeTestEntity;
import org.apache.cayenne.testdo.datetime.LocalTimeTestEntity;
import org.apache.cayenne.testdo.datetime.PeriodTestEntity;
+import org.apache.cayenne.unit.MySQLUnitDbAdapter;
import org.apache.cayenne.unit.UnitDbAdapter;
import org.apache.cayenne.unit.di.runtime.CayenneProjects;
import org.apache.cayenne.unit.di.runtime.RuntimeCase;
@@ -43,170 +46,227 @@ import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalField;
+import java.util.TimeZone;
import static org.junit.Assert.*;
@UseCayenneRuntime(CayenneProjects.DATE_TIME_PROJECT)
public class DateTimeTypesIT extends RuntimeCase {
- @Inject
- private DataContext context;
+ @Inject
+ private DataContext context;
- @Inject
- private UnitDbAdapter unitDbAdapter;
+ @Inject
+ private UnitDbAdapter unitDbAdapter;
- @Inject
- private DBHelper dbHelper;
+ @Inject
+ private DBHelper dbHelper;
- @Before
- public void before() throws SQLException {
- dbHelper.deleteAll("LOCAL_DATE_TEST");
- dbHelper.deleteAll("LOCAL_DATETIME_TEST");
- dbHelper.deleteAll("LOCAL_TIME_TEST");
- dbHelper.deleteAll("DURATION_TEST");
- dbHelper.deleteAll("PERIOD_TEST");
- }
+ @Before
+ public void before() throws SQLException {
+ dbHelper.deleteAll("LOCAL_DATE_TEST");
+ dbHelper.deleteAll("LOCAL_DATETIME_TEST");
+ dbHelper.deleteAll("LOCAL_TIME_TEST");
+ dbHelper.deleteAll("DURATION_TEST");
+ dbHelper.deleteAll("PERIOD_TEST");
+ }
- @Test
- public void testLocalDate_Null() {
- LocalDateTestEntity localDateTestEntity =
context.newObject(LocalDateTestEntity.class);
- localDateTestEntity.setDate(null);
+ @Test
+ public void testLocalDate_Null() {
+ LocalDateTestEntity localDateTestEntity =
context.newObject(LocalDateTestEntity.class);
+ localDateTestEntity.setDate(null);
- context.commitChanges();
+ context.commitChanges();
- LocalDateTestEntity testRead =
ObjectSelect.query(LocalDateTestEntity.class).selectOne(context);
+ LocalDateTestEntity testRead =
ObjectSelect.query(LocalDateTestEntity.class).selectOne(context);
- assertNull(testRead.getDate());
- }
+ assertNull(testRead.getDate());
+ }
- @Test
- public void testLocalDate() {
- LocalDateTestEntity localDateTestEntity =
context.newObject(LocalDateTestEntity.class);
- LocalDate localDate = LocalDate.now();
- localDateTestEntity.setDate(localDate);
+ @Test
+ public void testLocalDate() {
+ LocalDateTestEntity localDateTestEntity =
context.newObject(LocalDateTestEntity.class);
+ LocalDate localDate = LocalDate.now();
+ localDateTestEntity.setDate(localDate);
- context.commitChanges();
+ context.commitChanges();
- LocalDateTestEntity testRead =
ObjectSelect.query(LocalDateTestEntity.class).selectOne(context);
+ LocalDateTestEntity testRead =
ObjectSelect.query(LocalDateTestEntity.class).selectOne(context);
- assertNotNull(testRead.getDate());
- assertEquals(LocalDate.class, testRead.getDate().getClass());
- assertEquals(localDate, testRead.getDate());
- }
+ assertNotNull(testRead.getDate());
+ assertEquals(LocalDate.class, testRead.getDate().getClass());
+ assertEquals(localDate, testRead.getDate());
+ }
- @Test
- public void testLocalTime() {
- LocalTimeTestEntity localTimeTestEntity =
context.newObject(LocalTimeTestEntity.class);
- LocalTime localTime = LocalTime.now();
- localTimeTestEntity.setTime(localTime);
+ @Test
+ public void testLocalTime() {
+ LocalTimeTestEntity localTimeTestEntity =
context.newObject(LocalTimeTestEntity.class);
+ LocalTime localTime = LocalTime.now();
+ localTimeTestEntity.setTime(localTime);
- context.commitChanges();
+ context.commitChanges();
- LocalTimeTestEntity testRead =
ObjectSelect.query(LocalTimeTestEntity.class).selectOne(context);
+ LocalTimeTestEntity testRead =
ObjectSelect.query(LocalTimeTestEntity.class).selectOne(context);
- TemporalField testValue = unitDbAdapter.supportsPreciseTime()
- ? ChronoField.MILLI_OF_DAY
- : ChronoField.SECOND_OF_DAY;
+ TemporalField testValue = unitDbAdapter.supportsPreciseTime()
+ ? ChronoField.MILLI_OF_DAY
+ : ChronoField.SECOND_OF_DAY;
- assertNotNull(testRead.getTime());
- assertEquals(LocalTime.class, testRead.getTime().getClass());
- assertEquals(localTime.toSecondOfDay(),
testRead.getTime().toSecondOfDay());
- assertEquals(localTime.get(testValue),
testRead.getTime().get(testValue));
- }
+ assertNotNull(testRead.getTime());
+ assertEquals(LocalTime.class, testRead.getTime().getClass());
+ assertEquals(localTime.toSecondOfDay(),
testRead.getTime().toSecondOfDay());
+ assertEquals(localTime.get(testValue),
testRead.getTime().get(testValue));
+ }
- @Test
- public void testLocalDateTime() {
- LocalDateTimeTestEntity localDateTimeTestEntity =
context.newObject(LocalDateTimeTestEntity.class);
- // round up seconds fraction
- // reason: on MySQL field should be defined as
TIMESTAMP(fractionSecondsPrecision) to support it
- LocalDateTime localDateTime =
LocalDateTime.now().with(ChronoField.NANO_OF_SECOND, 0);
- localDateTimeTestEntity.setTimestamp(localDateTime);
+ @Test
+ public void testLocalDateTime() {
+ LocalDateTimeTestEntity localDateTimeTestEntity =
context.newObject(LocalDateTimeTestEntity.class);
+ // round up seconds fraction
+ // reason: on MySQL field should be defined as
TIMESTAMP(fractionSecondsPrecision) to support it
+ LocalDateTime localDateTime =
LocalDateTime.now().with(ChronoField.NANO_OF_SECOND, 0);
+ localDateTimeTestEntity.setTimestamp(localDateTime);
+
+ context.commitChanges();
+
+ LocalDateTimeTestEntity testRead =
ObjectSelect.query(LocalDateTimeTestEntity.class).selectOne(context);
- context.commitChanges();
+ assertNotNull(testRead.getTimestamp());
+ assertEquals(LocalDateTime.class, testRead.getTimestamp().getClass());
+ assertEquals(localDateTime, testRead.getTimestamp());
- LocalDateTimeTestEntity testRead =
ObjectSelect.query(LocalDateTimeTestEntity.class).selectOne(context);
+ }
+
+ @Test
+ public void testLocalDateTime_DST_Select() {
- assertNotNull(testRead.getTimestamp());
- assertEquals(LocalDateTime.class,
testRead.getTimestamp().getClass());
- assertEquals(localDateTime, testRead.getTimestamp());
+ if (!(unitDbAdapter instanceof MySQLUnitDbAdapter)) {
+ // skip other DB's for now until we can test across all of them
+ return;
+ }
+
+ // not using TableHelper to avoid PreparedStatement parameter
interactions for this timestamp
+ SQLExec.query("insert into LOCAL_DATETIME_TEST values (1, '2021-03-14
02:35:00')").execute(context);
- }
+ // set the TZ where this time doesn't exist because of the DST
switchover at 2 AM
+ TimeZone defaultTz = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
- @Test
- public void columnSelectWithLocalDateTime() {
- // round up seconds fraction
- // reason: on MySQL field should be defined as
TIMESTAMP(fractionSecondsPrecision) to support it
- LocalDateTime localDateTime =
LocalDateTime.now().with(ChronoField.NANO_OF_SECOND, 0);
+ try {
+ // LocalDateTime is TZ-agnostic, so the timestamp should not be
rewound to 3 AM just because it happens to
+ // match the DST "gap" in US timezones
+ LocalDateTime value =
ObjectSelect.query(LocalDateTimeTestEntity.class)
+ .column(LocalDateTimeTestEntity.TIMESTAMP)
+ .selectOne(context);
+
+ assertEquals(LocalDateTime.of(2021, 3, 14, 2, 35, 0), value);
+ } finally {
+ TimeZone.setDefault(defaultTz);
+ }
+ }
+
+ @Test
+ public void testLocalDateTime_DST_Insert() {
+
+ if (!(unitDbAdapter instanceof MySQLUnitDbAdapter)) {
+ // skip other DB's for now until we can test across all of them
+ return;
+ }
+
+ // set the TZ where this time doesn't exist because of the DST
switchover at 2 AM
+ TimeZone defaultTz = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
- LocalDateTimeTestEntity localDateTimeTestEntity =
context.newObject(LocalDateTimeTestEntity.class);
- localDateTimeTestEntity.setTimestamp(localDateTime);
+ try {
- context.commitChanges();
-
- LocalDateTime value =
ObjectSelect.query(LocalDateTimeTestEntity.class)
- .column(LocalDateTimeTestEntity.TIMESTAMP)
- .selectOne(context);
- assertEquals(localDateTime, value);
-
- LocalDateTime value2 =
ObjectSelect.query(LocalDateTimeTestEntity.class)
- .min(LocalDateTimeTestEntity.TIMESTAMP)
- .selectOne(context);
- assertEquals(localDateTime, value2);
- }
-
- @Test
- public void testDuration() {
- DurationTestEntity durationTestEntity =
context.newObject(DurationTestEntity.class);
- Duration duration = Duration.ofDays(10);
- durationTestEntity.setDurationBigInt(duration);
- durationTestEntity.setDurationDecimal(duration);
- durationTestEntity.setDurationInt(duration);
- durationTestEntity.setDurationLongVarchar(duration);
- durationTestEntity.setDurationNumeric(duration);
- durationTestEntity.setDurationVarchar(duration);
-
- context.commitChanges();
-
- DurationTestEntity testRead =
ObjectSelect.query(DurationTestEntity.class).selectOne(context);
-
- assertNotNull(testRead.getDurationBigInt());
- assertEquals(Duration.class,
testRead.getDurationBigInt().getClass());
- assertEquals(duration, testRead.getDurationBigInt());
-
- assertNotNull(testRead.getDurationDecimal());
- assertEquals(Duration.class,
testRead.getDurationDecimal().getClass());
- assertEquals(duration, testRead.getDurationDecimal());
-
- assertNotNull(testRead.getDurationInt());
- assertEquals(Duration.class,
testRead.getDurationInt().getClass());
- assertEquals(duration, testRead.getDurationInt());
-
- assertNotNull(testRead.getDurationLongVarchar());
- assertEquals(Duration.class,
testRead.getDurationLongVarchar().getClass());
- assertEquals(duration, testRead.getDurationLongVarchar());
-
- assertNotNull(testRead.getDurationNumeric());
- assertEquals(Duration.class,
testRead.getDurationNumeric().getClass());
- assertEquals(duration, testRead.getDurationNumeric());
-
- assertNotNull(testRead.getDurationVarchar());
- assertEquals(Duration.class,
testRead.getDurationVarchar().getClass());
- assertEquals(duration, testRead.getDurationVarchar());
- }
-
- @Test
- public void testPeriod() {
- PeriodTestEntity periodTestEntity =
context.newObject(PeriodTestEntity.class);
- Period period = Period.of(100, 10, 5);
- periodTestEntity.setPeriodField(period);
-
- context.commitChanges();
-
- PeriodTestEntity testRead =
ObjectSelect.query(PeriodTestEntity.class).selectOne(context);
-
- assertNotNull(testRead.getPeriodField());
- assertEquals(Period.class,
testRead.getPeriodField().getClass());
- assertEquals(period, testRead.getPeriodField());
- }
+ LocalDateTimeTestEntity o =
context.newObject(LocalDateTimeTestEntity.class);
+ o.setTimestamp(LocalDateTime.of(2021, 3, 14, 2, 35, 0));
+ context.commitChanges();
+
+ String val = SQLSelect
+ .scalarQuery("select CAST(TimestampField as CHAR) from
LOCAL_DATETIME_TEST", String.class)
+ .selectOne(context);
+
+ assertEquals("2021-03-14 02:35:00", val);
+ } finally {
+ TimeZone.setDefault(defaultTz);
+ }
+ }
+
+ @Test
+ public void columnSelectWithLocalDateTime() {
+ // round up seconds fraction
+ // reason: on MySQL field should be defined as
TIMESTAMP(fractionSecondsPrecision) to support it
+ LocalDateTime localDateTime =
LocalDateTime.now().with(ChronoField.NANO_OF_SECOND, 0);
+
+ LocalDateTimeTestEntity localDateTimeTestEntity =
context.newObject(LocalDateTimeTestEntity.class);
+ localDateTimeTestEntity.setTimestamp(localDateTime);
+
+ context.commitChanges();
+
+ LocalDateTime value = ObjectSelect.query(LocalDateTimeTestEntity.class)
+ .column(LocalDateTimeTestEntity.TIMESTAMP)
+ .selectOne(context);
+ assertEquals(localDateTime, value);
+
+ LocalDateTime value2 =
ObjectSelect.query(LocalDateTimeTestEntity.class)
+ .min(LocalDateTimeTestEntity.TIMESTAMP)
+ .selectOne(context);
+ assertEquals(localDateTime, value2);
+ }
+
+ @Test
+ public void testDuration() {
+ DurationTestEntity durationTestEntity =
context.newObject(DurationTestEntity.class);
+ Duration duration = Duration.ofDays(10);
+ durationTestEntity.setDurationBigInt(duration);
+ durationTestEntity.setDurationDecimal(duration);
+ durationTestEntity.setDurationInt(duration);
+ durationTestEntity.setDurationLongVarchar(duration);
+ durationTestEntity.setDurationNumeric(duration);
+ durationTestEntity.setDurationVarchar(duration);
+
+ context.commitChanges();
+
+ DurationTestEntity testRead =
ObjectSelect.query(DurationTestEntity.class).selectOne(context);
+
+ assertNotNull(testRead.getDurationBigInt());
+ assertEquals(Duration.class, testRead.getDurationBigInt().getClass());
+ assertEquals(duration, testRead.getDurationBigInt());
+
+ assertNotNull(testRead.getDurationDecimal());
+ assertEquals(Duration.class, testRead.getDurationDecimal().getClass());
+ assertEquals(duration, testRead.getDurationDecimal());
+
+ assertNotNull(testRead.getDurationInt());
+ assertEquals(Duration.class, testRead.getDurationInt().getClass());
+ assertEquals(duration, testRead.getDurationInt());
+
+ assertNotNull(testRead.getDurationLongVarchar());
+ assertEquals(Duration.class,
testRead.getDurationLongVarchar().getClass());
+ assertEquals(duration, testRead.getDurationLongVarchar());
+
+ assertNotNull(testRead.getDurationNumeric());
+ assertEquals(Duration.class, testRead.getDurationNumeric().getClass());
+ assertEquals(duration, testRead.getDurationNumeric());
+
+ assertNotNull(testRead.getDurationVarchar());
+ assertEquals(Duration.class, testRead.getDurationVarchar().getClass());
+ assertEquals(duration, testRead.getDurationVarchar());
+ }
+
+ @Test
+ public void testPeriod() {
+ PeriodTestEntity periodTestEntity =
context.newObject(PeriodTestEntity.class);
+ Period period = Period.of(100, 10, 5);
+ periodTestEntity.setPeriodField(period);
+
+ context.commitChanges();
+
+ PeriodTestEntity testRead =
ObjectSelect.query(PeriodTestEntity.class).selectOne(context);
+
+ assertNotNull(testRead.getPeriodField());
+ assertEquals(Period.class, testRead.getPeriodField().getClass());
+ assertEquals(period, testRead.getPeriodField());
+ }
}
diff --git
a/cayenne/src/test/java/org/apache/cayenne/unit/testcontainers/MysqlContainerProvider.java
b/cayenne/src/test/java/org/apache/cayenne/unit/testcontainers/MysqlContainerProvider.java
index aabfbe6e3..e52c98434 100644
---
a/cayenne/src/test/java/org/apache/cayenne/unit/testcontainers/MysqlContainerProvider.java
+++
b/cayenne/src/test/java/org/apache/cayenne/unit/testcontainers/MysqlContainerProvider.java
@@ -18,14 +18,14 @@
****************************************************************/
package org.apache.cayenne.unit.testcontainers;
-import java.util.Calendar;
-
import org.apache.cayenne.dba.JdbcAdapter;
import org.apache.cayenne.dba.mysql.MySQLAdapter;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
+import java.util.Calendar;
+
public class MysqlContainerProvider extends TestContainerProvider {
@Override
diff --git a/pom.xml b/pom.xml
index 3923d68e3..2330a835a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,7 @@
<slf4j.version>1.7.36</slf4j.version>
<ant.version>1.10.12</ant.version>
<testcontainers.version>1.20.1</testcontainers.version>
+ <mysql.connector.version>8.0.29</mysql.connector.version>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
@@ -399,6 +400,12 @@
<version>10.14.2.0</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <version>${mysql.connector.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-matchers</artifactId>
@@ -1031,7 +1038,6 @@
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
- <version>8.0.29</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -1048,7 +1054,6 @@
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
- <version>8.0.29</version>
<scope>test</scope>
</dependency>
</dependencies>