From f0153614998209d387539f85c88bef9f865908b5 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 09:36:17 +0000 Subject: [PATCH 01/10] chore(main): release 2.13.3-SNAPSHOT (#1364) :robot: I have created a release *beep* *boop* --- ### Updating meta-information for bleeding-edge SNAPSHOT release. --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- versions.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 8ea55af4b..ee49db7d9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 google-cloud-spanner-jdbc - 2.13.2 + 2.13.3-SNAPSHOT jar Google Cloud Spanner JDBC https://github.com/googleapis/java-spanner-jdbc diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 038e5e0d3..ec9a8dd95 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.13.2 + 2.13.3-SNAPSHOT diff --git a/versions.txt b/versions.txt index fc35a6a01..094a46df1 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.13.2:2.13.2 +google-cloud-spanner-jdbc:2.13.2:2.13.3-SNAPSHOT From ce52d07c308bcde0ed1b0c9f4d3556db2590f722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Tue, 26 Sep 2023 14:45:03 +0200 Subject: [PATCH 02/10] docs: add sample for Spring Data MyBatis (#1352) * docs: add sample for Spring Data MyBatis Adds a sample application for using Spring Data and MyBatis with the Cloud Spanner JDBC driver and a PostgreSQL-dialect database. * chore: update sample database name --- samples/spring-data-mybatis/README.md | 93 ++ samples/spring-data-mybatis/pom.xml | 112 +++ .../cloud/spanner/sample/Application.java | 133 +++ .../cloud/spanner/sample/DatabaseSeeder.java | 342 +++++++ .../spanner/sample/JdbcConfiguration.java | 70 ++ .../sample/entities/AbstractEntity.java | 73 ++ .../cloud/spanner/sample/entities/Album.java | 84 ++ .../spanner/sample/entities/Concert.java | 78 ++ .../cloud/spanner/sample/entities/Singer.java | 73 ++ .../cloud/spanner/sample/entities/Track.java | 66 ++ .../cloud/spanner/sample/entities/Venue.java | 43 + .../spanner/sample/mappers/AlbumMapper.java | 48 + .../spanner/sample/mappers/ConcertMapper.java | 29 + .../spanner/sample/mappers/SingerMapper.java | 59 ++ .../spanner/sample/mappers/TrackMapper.java | 37 + .../spanner/sample/mappers/VenueMapper.java | 29 + .../spanner/sample/service/AlbumService.java | 49 + .../spanner/sample/service/SingerService.java | 67 ++ .../main/resources/application-cs.properties | 9 + .../main/resources/application-pg.properties | 7 + .../src/main/resources/application.properties | 13 + .../src/main/resources/create_schema.sql | 68 ++ .../src/main/resources/drop_schema.sql | 5 + .../cloud/spanner/sample/ApplicationTest.java | 879 ++++++++++++++++++ .../test/resources/application-cs.properties | 9 + .../test/resources/application-pg.properties | 7 + .../src/test/resources/application.properties | 13 + 27 files changed, 2495 insertions(+) create mode 100644 samples/spring-data-mybatis/README.md create mode 100644 samples/spring-data-mybatis/pom.xml create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/Application.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/DatabaseSeeder.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/JdbcConfiguration.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/AbstractEntity.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Album.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Concert.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Singer.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Track.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Venue.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/AlbumMapper.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/ConcertMapper.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/SingerMapper.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/TrackMapper.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/VenueMapper.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/service/AlbumService.java create mode 100644 samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/service/SingerService.java create mode 100644 samples/spring-data-mybatis/src/main/resources/application-cs.properties create mode 100644 samples/spring-data-mybatis/src/main/resources/application-pg.properties create mode 100644 samples/spring-data-mybatis/src/main/resources/application.properties create mode 100644 samples/spring-data-mybatis/src/main/resources/create_schema.sql create mode 100644 samples/spring-data-mybatis/src/main/resources/drop_schema.sql create mode 100644 samples/spring-data-mybatis/src/test/java/com/google/cloud/spanner/sample/ApplicationTest.java create mode 100644 samples/spring-data-mybatis/src/test/resources/application-cs.properties create mode 100644 samples/spring-data-mybatis/src/test/resources/application-pg.properties create mode 100644 samples/spring-data-mybatis/src/test/resources/application.properties diff --git a/samples/spring-data-mybatis/README.md b/samples/spring-data-mybatis/README.md new file mode 100644 index 000000000..02cf135d4 --- /dev/null +++ b/samples/spring-data-mybatis/README.md @@ -0,0 +1,93 @@ +# Spring Data MyBatis Sample Application with Cloud Spanner PostgreSQL + +This sample application shows how to develop portable applications using Spring Data MyBatis in +combination with Cloud Spanner PostgreSQL. This application can be configured to run on either a +[Cloud Spanner PostgreSQL](https://cloud.google.com/spanner/docs/postgresql-interface) database or +an open-source PostgreSQL database. The only change that is needed to switch between the two is +changing the active Spring profile that is used by the application. + +The application uses the Cloud Spanner JDBC driver to connect to Cloud Spanner PostgreSQL, and it +uses the PostgreSQL JDBC driver to connect to open-source PostgreSQL. Spring Data MyBatis works with +both drivers and offers a single consistent API to the application developer, regardless of the +actual database or JDBC driver being used. + +This sample shows: + +1. How to use Spring Data MyBatis with Cloud Spanner PostgreSQL. +2. How to develop a portable application that runs on both Google Cloud Spanner PostgreSQL and + open-source PostgreSQL with the same code base. +3. How to use bit-reversed sequences to automatically generate primary key values for entities. + +__NOTE__: This application does __not require PGAdapter__. Instead, it connects to Cloud Spanner +PostgreSQL using the Cloud Spanner JDBC driver. + +## Cloud Spanner PostgreSQL + +Cloud Spanner PostgreSQL provides language support by expressing Spanner database functionality +through a subset of open-source PostgreSQL language constructs, with extensions added to support +Spanner functionality like interleaved tables and hinting. + +The PostgreSQL interface makes the capabilities of Spanner —__fully managed, unlimited scale, strong +consistency, high performance, and up to 99.999% global availability__— accessible using the +PostgreSQL dialect. Unlike other services that manage actual PostgreSQL database instances, Spanner +uses PostgreSQL-compatible syntax to expose its existing scale-out capabilities. This provides +familiarity for developers and portability for applications, but not 100% PostgreSQL compatibility. +The SQL syntax that Spanner supports is semantically equivalent PostgreSQL, meaning schemas +and queries written against the PostgreSQL interface can be easily ported to another PostgreSQL +environment. + +This sample showcases this portability with an application that works on both Cloud Spanner PostgreSQL +and open-source PostgreSQL with the same code base. + +## MyBatis Spring +[MyBatis Spring](http://mybatis.org/spring/) integrates MyBatis with the popular Java Spring +framework. This allows MyBatis to participate in Spring transactions and to automatically inject +MyBatis mappers into other beans. + +## Sample Application + +This sample shows how to create a portable application using Spring Data MyBatis and the Cloud Spanner +PostgreSQL dialect. The application works on both Cloud Spanner PostgreSQL and open-source +PostgreSQL. You can switch between the two by changing the active Spring profile: +* Profile `cs` runs the application on Cloud Spanner PostgreSQL. +* Profile `pg` runs the application on open-source PostgreSQL. + +The default profile is `cs`. You can change the default profile by modifying the +[application.properties](src/main/resources/application.properties) file. + +### Running the Application + +1. Choose the database system that you want to use by choosing a profile. The default profile is + `cs`, which runs the application on Cloud Spanner PostgreSQL. Modify the default profile in the + [application.properties](src/main/resources/application.properties) file. +2. Modify either [application-cs.properties](src/main/resources/application-cs.properties) or + [application-pg.properties](src/main/resources/application-pg.properties) to point to an existing + database. If you use Cloud Spanner, the database that the configuration file references must be a + database that uses the PostgreSQL dialect. +3. Run the application with `mvn spring-boot:run`. + +### Main Application Components + +The main application components are: +* [DatabaseSeeder.java](src/main/java/com/google/cloud/spanner/sample/DatabaseSeeder.java): This + class is responsible for creating the database schema and inserting some initial test data. The + schema is created from the [create_schema.sql](src/main/resources/create_schema.sql) file. The + `DatabaseSeeder` class loads this file into memory and executes it on the active database using + standard JDBC APIs. The class also removes Cloud Spanner-specific extensions to the PostgreSQL + dialect when the application runs on open-source PostgreSQL. +* [JdbcConfiguration.java](src/main/java/com/google/cloud/spanner/sample/JdbcConfiguration.java): + This utility class is used to determine whether the application is running on Cloud Spanner + PostgreSQL or open-source PostgreSQL. This can be used if you have specific features that should + only be executed on one of the two systems. +* [AbstractEntity.java](src/main/java/com/google/cloud/spanner/sample/entities/AbstractEntity.java): + This is the shared base class for all entities in this sample application. It defines a number of + standard attributes, such as the identifier (primary key). The primary key is automatically + generated using a (bit-reversed) sequence. [Bit-reversed sequential values](https://cloud.google.com/spanner/docs/schema-design#bit_reverse_primary_key) + are considered a good choice for primary keys on Cloud Spanner. +* [Application.java](src/main/java/com/google/cloud/spanner/sample/Application.java): The starter + class of the application. It contains a command-line runner that executes a selection of queries + and updates on the database. +* [SingerService](src/main/java/com/google/cloud/spanner/sample/service/SingerService.java) and + [AlbumService](src/main/java/com/google/cloud/spanner/sample/service/SingerService.java) are + standard Spring service beans that contain business logic that can be executed as transactions. + This includes both read/write and read-only transactions. diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml new file mode 100644 index 000000000..2d9de56fb --- /dev/null +++ b/samples/spring-data-mybatis/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + org.example + cloud-spanner-spring-data-mybatis-example + 1.0-SNAPSHOT + + Sample application showing how to use Spring Data MyBatis with Cloud Spanner PostgreSQL. + + + org.springframework.boot + spring-boot-starter-parent + 3.1.3 + + + + 17 + 17 + 17 + UTF-8 + + + + + + org.springframework.data + spring-data-bom + 2023.0.3 + import + pom + + + com.google.cloud + libraries-bom + 26.22.0 + import + pom + + + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.2 + + + org.mybatis.dynamic-sql + mybatis-dynamic-sql + 1.5.0 + + + + + com.google.cloud + google-cloud-spanner-jdbc + 2.12.1 + + + org.postgresql + postgresql + 42.6.0 + + + + com.google.collections + google-collections + 1.0 + + + + + com.google.cloud + google-cloud-spanner + test-jar + test + + + com.google.api + gax-grpc + testlib + test + + + junit + junit + 4.13.2 + test + + + + + + + com.spotify.fmt + fmt-maven-plugin + 2.20 + + + + format + + + + + + + diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/Application.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/Application.java new file mode 100644 index 000000000..04286b5a9 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/Application.java @@ -0,0 +1,133 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample; + +import com.google.cloud.spanner.sample.entities.Album; +import com.google.cloud.spanner.sample.entities.Singer; +import com.google.cloud.spanner.sample.entities.Track; +import com.google.cloud.spanner.sample.mappers.AlbumMapper; +import com.google.cloud.spanner.sample.mappers.SingerMapper; +import com.google.cloud.spanner.sample.service.AlbumService; +import com.google.cloud.spanner.sample.service.SingerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application implements CommandLineRunner { + private static final Logger logger = LoggerFactory.getLogger(Application.class); + + public static void main(String[] args) { + SpringApplication.run(Application.class, args).close(); + } + + private final DatabaseSeeder databaseSeeder; + + private final SingerService singerService; + + private final AlbumService albumService; + + private final SingerMapper singerMapper; + + private final AlbumMapper albumMapper; + + public Application( + SingerService singerService, + AlbumService albumService, + DatabaseSeeder databaseSeeder, + SingerMapper singerMapper, + AlbumMapper albumMapper) { + this.databaseSeeder = databaseSeeder; + this.singerService = singerService; + this.albumService = albumService; + this.singerMapper = singerMapper; + this.albumMapper = albumMapper; + } + + @Override + public void run(String... args) { + + // Set the system property 'drop_schema' to true to drop any existing database + // schema when the application is executed. + if (Boolean.parseBoolean(System.getProperty("drop_schema", "false"))) { + logger.info("Dropping existing schema if it exists"); + databaseSeeder.dropDatabaseSchemaIfExists(); + } + + logger.info("Creating database schema if it does not already exist"); + databaseSeeder.createDatabaseSchemaIfNotExists(); + logger.info("Deleting existing test data"); + databaseSeeder.deleteTestData(); + logger.info("Inserting fresh test data"); + databaseSeeder.insertTestData(); + + Iterable allSingers = singerMapper.findAll(); + for (Singer singer : allSingers) { + logger.info( + "Found singer: {} with {} albums", + singer, + albumMapper.countAlbumsBySingerId(singer.getId())); + for (Album album : albumMapper.findAlbumsBySingerId(singer.getId())) { + logger.info("\tAlbum: {}, released at {}", album, album.getReleaseDate()); + } + } + + // Create a new singer and three albums in a transaction. + Singer insertedSinger = + singerService.createSingerAndAlbums( + new Singer("Amethyst", "Jiang"), + new Album(DatabaseSeeder.randomTitle()), + new Album(DatabaseSeeder.randomTitle()), + new Album(DatabaseSeeder.randomTitle())); + logger.info( + "Inserted singer {} {} {}", + insertedSinger.getId(), + insertedSinger.getFirstName(), + insertedSinger.getLastName()); + + // Create a new Album and some Tracks in a read/write transaction. + // Track is an interleaved table. + Album album = new Album(DatabaseSeeder.randomTitle()); + album.setSingerId(insertedSinger.getId()); + albumService.createAlbumAndTracks( + album, + new Track(album, 1, DatabaseSeeder.randomTitle(), 3.14d), + new Track(album, 2, DatabaseSeeder.randomTitle(), 3.14d), + new Track(album, 3, DatabaseSeeder.randomTitle(), 3.14d), + new Track(album, 4, DatabaseSeeder.randomTitle(), 3.14d), + new Track(album, 5, DatabaseSeeder.randomTitle(), 3.14d), + new Track(album, 6, DatabaseSeeder.randomTitle(), 3.14d), + new Track(album, 7, DatabaseSeeder.randomTitle(), 3.14d)); + logger.info("Inserted album {}", album.getTitle()); + + // List all singers that have a last name starting with an 'J'. + logger.info("All singers with a last name starting with an 'J':"); + for (Singer singer : singerMapper.findSingersByLastNameStartingWith("J")) { + logger.info("\t{}", singer.getFullName()); + } + + // The singerService.listSingersWithLastNameStartingWith(..) method uses a read-only + // transaction. You should prefer read-only transactions to read/write transactions whenever + // possible, as read-only transactions do not take locks. + logger.info("All singers with a last name starting with an 'A', 'B', or 'C'."); + for (Singer singer : singerService.listSingersWithLastNameStartingWith("A", "B", "C")) { + logger.info("\t{}", singer.getFullName()); + } + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/DatabaseSeeder.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/DatabaseSeeder.java new file mode 100644 index 000000000..eabd04c3b --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/DatabaseSeeder.java @@ -0,0 +1,342 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.cloud.spanner.sample.entities.Singer; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.FileCopyUtils; + +/** This component creates the database schema and seeds it with some random test data. */ +@Component +public class DatabaseSeeder { + + /** Randomly generated names. */ + public static final ImmutableList INITIAL_SINGERS = + ImmutableList.of( + new Singer("Aaliyah", "Smith"), + new Singer("Benjamin", "Jones"), + new Singer("Chloe", "Brown"), + new Singer("David", "Williams"), + new Singer("Elijah", "Johnson"), + new Singer("Emily", "Miller"), + new Singer("Gabriel", "Garcia"), + new Singer("Hannah", "Rodriguez"), + new Singer("Isabella", "Hernandez"), + new Singer("Jacob", "Perez")); + + private static final Random RANDOM = new Random(); + + private final JdbcTemplate jdbcTemplate; + + @Value("classpath:create_schema.sql") + private Resource createSchemaFile; + + @Value("classpath:drop_schema.sql") + private Resource dropSchemaFile; + + /** This value is determined once using a system query, and then cached. */ + private final Supplier isCloudSpannerPG; + + public DatabaseSeeder(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.isCloudSpannerPG = + Suppliers.memoize(() -> JdbcConfiguration.isCloudSpannerPG(jdbcTemplate)); + } + + /** Reads a resource file into a string. */ + private static String resourceAsString(Resource resource) { + try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) { + return FileCopyUtils.copyToString(reader); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Returns true if this application is currently running on a Cloud Spanner PostgreSQL database, + * and false if it is running on an open-source PostgreSQL database. + */ + private boolean isCloudSpanner() { + return isCloudSpannerPG.get(); + } + + /** + * Removes all statements that start with a 'skip_on_open_source_pg' comment if the application is + * running on open-source PostgreSQL. This ensures that we can use the same DDL script both on + * Cloud Spanner and on open-source PostgreSQL. It also removes any empty statements in the given + * array. + */ + private String[] updateDdlStatements(String[] statements) { + if (!isCloudSpanner()) { + for (int i = 0; i < statements.length; i++) { + // Replace any line that starts with '/* skip_on_open_source_pg */' with an empty string. + statements[i] = + statements[i].replaceAll("(?m)^\\s*/\\*\\s*skip_on_open_source_pg\\s*\\*/.+$", ""); + } + } + // Remove any empty statements from the script. + return Arrays.stream(statements) + .filter(statement -> !statement.isBlank()) + .toArray(String[]::new); + } + + /** Creates the database schema if it does not yet exist. */ + public void createDatabaseSchemaIfNotExists() { + // We can safely just split the script based on ';', as we know that there are no literals or + // other strings that contain semicolons in the script. + String[] statements = updateDdlStatements(resourceAsString(createSchemaFile).split(";")); + // Execute all the DDL statements as a JDBC batch. That ensures that Cloud Spanner will apply + // all statements in a single DDL batch, which again is a lot more efficient than executing them + // one-by-one. + jdbcTemplate.batchUpdate(statements); + } + + /** Drops the database schema if it exists. */ + public void dropDatabaseSchemaIfExists() { + // We can safely just split the script based on ';', as we know that there are no literals or + // other strings that contain semicolons in the script. + String[] statements = updateDdlStatements(resourceAsString(dropSchemaFile).split(";")); + // Execute all the DDL statements as a JDBC batch. That ensures that Cloud Spanner will apply + // all statements in a single DDL batch, which again is a lot more efficient than executing them + // one-by-one. + jdbcTemplate.batchUpdate(statements); + } + + /** Deletes all data currently in the sample tables. */ + public void deleteTestData() { + // Delete all data in one batch. + jdbcTemplate.batchUpdate( + "delete from concerts", + "delete from venues", + "delete from tracks", + "delete from albums", + "delete from singers"); + } + + /** Inserts some initial test data into the database. */ + public void insertTestData() { + jdbcTemplate.batchUpdate( + "insert into singers (first_name, last_name) values (?, ?)", + new BatchPreparedStatementSetter() { + @Override + public void setValues(@Nonnull PreparedStatement preparedStatement, int i) + throws SQLException { + preparedStatement.setString(1, INITIAL_SINGERS.get(i).getFirstName()); + preparedStatement.setString(2, INITIAL_SINGERS.get(i).getLastName()); + } + + @Override + public int getBatchSize() { + return INITIAL_SINGERS.size(); + } + }); + + List singerIds = + jdbcTemplate.query( + "select id from singers", + resultSet -> { + ImmutableList.Builder builder = ImmutableList.builder(); + while (resultSet.next()) { + builder.add(resultSet.getLong(1)); + } + return builder.build(); + }); + jdbcTemplate.batchUpdate( + "insert into albums (title, marketing_budget, release_date, cover_picture, singer_id) values (?, ?, ?, ?, ?)", + new BatchPreparedStatementSetter() { + @Override + public void setValues(@Nonnull PreparedStatement preparedStatement, int i) + throws SQLException { + preparedStatement.setString(1, randomTitle()); + preparedStatement.setBigDecimal(2, randomBigDecimal()); + preparedStatement.setObject(3, randomDate()); + preparedStatement.setBytes(4, randomBytes()); + preparedStatement.setLong(5, randomElement(singerIds)); + } + + @Override + public int getBatchSize() { + return INITIAL_SINGERS.size() * 20; + } + }); + } + + /** Generates a random title for an album or a track. */ + static String randomTitle() { + return randomElement(ADJECTIVES) + " " + randomElement(NOUNS); + } + + /** Returns a random element from the given list. */ + static T randomElement(List list) { + return list.get(RANDOM.nextInt(list.size())); + } + + /** Generates a random {@link BigDecimal}. */ + BigDecimal randomBigDecimal() { + return BigDecimal.valueOf(RANDOM.nextDouble()); + } + + /** Generates a random {@link LocalDate}. */ + static LocalDate randomDate() { + return LocalDate.of(RANDOM.nextInt(200) + 1800, RANDOM.nextInt(12) + 1, RANDOM.nextInt(28) + 1); + } + + /** Generates a random byte array with a length between 4 and 1024 bytes. */ + static byte[] randomBytes() { + int size = RANDOM.nextInt(1020) + 4; + byte[] res = new byte[size]; + RANDOM.nextBytes(res); + return res; + } + + /** Some randomly generated nouns that are used to generate random titles. */ + private static final ImmutableList NOUNS = + ImmutableList.of( + "apple", + "banana", + "cherry", + "dog", + "elephant", + "fish", + "grass", + "house", + "key", + "lion", + "monkey", + "nail", + "orange", + "pen", + "queen", + "rain", + "shoe", + "tree", + "umbrella", + "van", + "whale", + "xylophone", + "zebra"); + + /** Some randomly generated adjectives that are used to generate random titles. */ + private static final ImmutableList ADJECTIVES = + ImmutableList.of( + "able", + "angelic", + "artistic", + "athletic", + "attractive", + "autumnal", + "calm", + "careful", + "cheerful", + "clever", + "colorful", + "confident", + "courageous", + "creative", + "curious", + "daring", + "determined", + "different", + "dreamy", + "efficient", + "elegant", + "energetic", + "enthusiastic", + "exciting", + "expressive", + "faithful", + "fantastic", + "funny", + "gentle", + "gifted", + "great", + "happy", + "helpful", + "honest", + "hopeful", + "imaginative", + "intelligent", + "interesting", + "inventive", + "joyful", + "kind", + "knowledgeable", + "loving", + "loyal", + "magnificent", + "mature", + "mysterious", + "natural", + "nice", + "optimistic", + "peaceful", + "perfect", + "pleasant", + "powerful", + "proud", + "quick", + "relaxed", + "reliable", + "responsible", + "romantic", + "safe", + "sensitive", + "sharp", + "simple", + "sincere", + "skillful", + "smart", + "sociable", + "strong", + "successful", + "sweet", + "talented", + "thankful", + "thoughtful", + "unique", + "upbeat", + "valuable", + "victorious", + "vivacious", + "warm", + "wealthy", + "wise", + "wonderful", + "worthy", + "youthful"); +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/JdbcConfiguration.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/JdbcConfiguration.java new file mode 100644 index 000000000..2398add31 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/JdbcConfiguration.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample; + +import com.google.cloud.spanner.jdbc.JdbcSqlException; +import com.google.rpc.Code; +import java.util.Objects; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.jdbc.core.JdbcOperations; + +@Configuration +public class JdbcConfiguration { + + /** Returns true if the current database is a Cloud Spanner PostgreSQL database. */ + public static boolean isCloudSpannerPG(JdbcOperations operations) { + try { + Long value = + operations.queryForObject( + "select 1 " + + "from information_schema.database_options " + + "where schema_name='public' " + + "and option_name='database_dialect' " + + "and option_value='POSTGRESQL'", + Long.class); + // Shouldn't really be anything else than 1 if the query succeeded, but this avoids complaints + // from the compiler. + if (Objects.equals(1L, value)) { + return true; + } + } catch (IncorrectResultSizeDataAccessException exception) { + // This indicates that it is a valid Cloud Spanner database, but not one that uses the + // PostgreSQL dialect. + throw new RuntimeException( + "The selected Cloud Spanner database does not use the PostgreSQL dialect"); + } catch (DataAccessException exception) { + if (exception.getCause() instanceof JdbcSqlException) { + JdbcSqlException jdbcSqlException = (JdbcSqlException) exception.getCause(); + if (jdbcSqlException.getCode() == Code.PERMISSION_DENIED + || jdbcSqlException.getCode() == Code.NOT_FOUND) { + throw new RuntimeException( + "Failed to get the dialect of the Cloud Spanner database. " + + "Please check that the selected database exists and that you have permission to access it. " + + "Cause: " + + exception.getCause().getMessage(), + exception.getCause()); + } + } + // ignore and fall through + } catch (Throwable exception) { + // ignore and fall through + } + return false; + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/AbstractEntity.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/AbstractEntity.java new file mode 100644 index 000000000..2dbffd0e7 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/AbstractEntity.java @@ -0,0 +1,73 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.entities; + +import java.time.OffsetDateTime; + +public abstract class AbstractEntity { + + /** This ID is generated using a (bit-reversed) sequence. */ + private Long id; + + private OffsetDateTime createdAt; + + private OffsetDateTime updatedAt; + + @Override + public boolean equals(Object o) { + if (!(o instanceof AbstractEntity)) { + return false; + } + AbstractEntity other = (AbstractEntity) o; + if (this == other) { + return true; + } + return this.getClass().equals(other.getClass()) + && this.id != null + && other.id != null + && this.id.equals(other.id); + } + + @Override + public int hashCode() { + return this.id == null ? 0 : this.id.hashCode(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public OffsetDateTime getCreatedAt() { + return createdAt; + } + + protected void setCreatedAt(OffsetDateTime createdAt) { + this.createdAt = createdAt; + } + + public OffsetDateTime getUpdatedAt() { + return updatedAt; + } + + protected void setUpdatedAt(OffsetDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Album.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Album.java new file mode 100644 index 000000000..57df330bf --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Album.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.entities; + +import java.math.BigDecimal; +import java.time.LocalDate; + +public class Album extends AbstractEntity { + + private String title; + + private BigDecimal marketingBudget; + + private LocalDate releaseDate; + + private byte[] coverPicture; + + private Long singerId; + + public Album() {} + + public Album(String title) { + this.title = title; + } + + @Override + public String toString() { + return getTitle(); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public BigDecimal getMarketingBudget() { + return marketingBudget; + } + + public void setMarketingBudget(BigDecimal marketingBudget) { + this.marketingBudget = marketingBudget; + } + + public LocalDate getReleaseDate() { + return releaseDate; + } + + public void setReleaseDate(LocalDate releaseDate) { + this.releaseDate = releaseDate; + } + + public byte[] getCoverPicture() { + return coverPicture; + } + + public void setCoverPicture(byte[] coverPicture) { + this.coverPicture = coverPicture; + } + + public Long getSingerId() { + return singerId; + } + + public void setSingerId(Long singerId) { + this.singerId = singerId; + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Concert.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Concert.java new file mode 100644 index 000000000..fa219b16d --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Concert.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.entities; + +import java.time.OffsetDateTime; + +public class Concert extends AbstractEntity { + + private Long venueId; + + private Long singerId; + + private String name; + + private OffsetDateTime startTime; + + private OffsetDateTime endTime; + + public Concert(Venue venue, Singer singer, String name) { + this.venueId = venue.getId(); + this.singerId = singer.getId(); + this.name = name; + } + + public Long getVenueId() { + return venueId; + } + + public void setVenueId(Long venueId) { + this.venueId = venueId; + } + + public Long getSingerId() { + return singerId; + } + + public void setSingerId(Long singerId) { + this.singerId = singerId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public OffsetDateTime getStartTime() { + return startTime; + } + + public void setStartTime(OffsetDateTime startTime) { + this.startTime = startTime; + } + + public OffsetDateTime getEndTime() { + return endTime; + } + + public void setEndTime(OffsetDateTime endTime) { + this.endTime = endTime; + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Singer.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Singer.java new file mode 100644 index 000000000..90dc0e1e0 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Singer.java @@ -0,0 +1,73 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.entities; + +public class Singer extends AbstractEntity { + + private String firstName; + + private String lastName; + + /** The full name is generated by the database using a generated column. */ + private String fullName; + + private Boolean active; + + public Singer() {} + + public Singer(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + @Override + public String toString() { + return getFullName(); + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Track.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Track.java new file mode 100644 index 000000000..51fb756c9 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Track.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.entities; + +/** + * The "tracks" table is interleaved in "albums". That means that the first part of the primary key + * (the "id" column) references the Album that this Track belongs to. That again means that we do + * not auto-generate the id for this entity. + */ +public class Track extends AbstractEntity { + + /** + * This is the second part of the primary key of a Track. The first part, the 'id' column is + * defined in the {@link AbstractEntity} super class. + */ + private int trackNumber; + + private String title; + + private Double sampleRate; + + public Track(Album album, int trackNumber, String title, Double sampleRate) { + setId(album.getId()); + this.trackNumber = trackNumber; + this.title = title; + this.sampleRate = sampleRate; + } + + public int getTrackNumber() { + return trackNumber; + } + + public void setTrackNumber(int trackNumber) { + this.trackNumber = trackNumber; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Double getSampleRate() { + return sampleRate; + } + + public void setSampleRate(Double sampleRate) { + this.sampleRate = sampleRate; + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Venue.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Venue.java new file mode 100644 index 000000000..ff7ee5049 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/entities/Venue.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.entities; + +public class Venue extends AbstractEntity { + private String name; + + private String description; + + public Venue(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/AlbumMapper.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/AlbumMapper.java new file mode 100644 index 000000000..85a05f28e --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/AlbumMapper.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.mappers; + +import com.google.cloud.spanner.sample.entities.Album; +import java.util.List; +import java.util.Optional; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface AlbumMapper { + + @Select("SELECT * FROM albums WHERE id = #{albumId}") + Album get(@Param("albumId") long albumId); + + @Select("SELECT * FROM albums LIMIT 1") + Optional getFirst(); + + @Select("SELECT COUNT(1) FROM albums WHERE singer_id = #{singerId}") + long countAlbumsBySingerId(@Param("singerId") long singerId); + + @Select("SELECT * FROM albums WHERE singer_id = #{singerId}") + List findAlbumsBySingerId(@Param("singerId") long singerId); + + @Insert( + "INSERT INTO albums (title, marketing_budget, release_date, cover_picture, singer_id) " + + "VALUES (#{title}, #{marketingBudget}, #{releaseDate}, #{coverPicture}, #{singerId})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insert(Album album); +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/ConcertMapper.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/ConcertMapper.java new file mode 100644 index 000000000..d268b4327 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/ConcertMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.mappers; + +import com.google.cloud.spanner.sample.entities.Venue; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface ConcertMapper { + + @Select("SELECT * FROM concerts WHERE id = #{concertId}") + Venue get(@Param("concertId") long concertId); +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/SingerMapper.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/SingerMapper.java new file mode 100644 index 000000000..7f55466fe --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/SingerMapper.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.mappers; + +import com.google.cloud.spanner.sample.entities.Singer; +import java.util.List; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +@Mapper +public interface SingerMapper { + + @Select("SELECT * FROM singers WHERE id = #{singerId}") + Singer get(@Param("singerId") long singerId); + + @Select("SELECT * FROM singers ORDER BY last_name, first_name, id") + List findAll(); + + @Select("SELECT * FROM singers WHERE starts_with(last_name, #{lastName})") + List findSingersByLastNameStartingWith(@Param("lastName") String lastName); + + /** + * Inserts a new singer record and returns both the generated primary key value and the generated + * full name. + */ + @Insert( + "INSERT INTO singers (first_name, last_name, active) " + + "VALUES (#{firstName}, #{lastName}, #{active})") + @Options(useGeneratedKeys = true, keyProperty = "id,fullName") + int insert(Singer singer); + + /** Updates an existing singer and returns the generated full name. */ + @Update( + "UPDATE singers SET " + + "first_name=#{first_name}, " + + "last_name=#{last_name}, " + + "active=#{active} " + + "WHERE id=#{id}") + @Options(useGeneratedKeys = true, keyProperty = "fullName") + int update(Singer singer); +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/TrackMapper.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/TrackMapper.java new file mode 100644 index 000000000..5972e2711 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/TrackMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.mappers; + +import com.google.cloud.spanner.sample.entities.Track; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface TrackMapper { + + @Select("SELECT * FROM tracks WHERE id = #{albumId} AND track_number = #{trackNumber}") + Track get(@Param("albumId") long albumId, @Param("trackNumber") long trackNumber); + + @Insert( + "INSERT INTO tracks (id, track_number, title, sample_rate) " + + "VALUES (#{id}, #{trackNumber}, #{title}, #{sampleRate})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insert(Track track); +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/VenueMapper.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/VenueMapper.java new file mode 100644 index 000000000..ab81c45cd --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/mappers/VenueMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.mappers; + +import com.google.cloud.spanner.sample.entities.Venue; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +@Mapper +public interface VenueMapper { + + @Select("SELECT * FROM venues WHERE id = #{venueId}") + Venue get(@Param("venueId") long venueId); +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/service/AlbumService.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/service/AlbumService.java new file mode 100644 index 000000000..4e326a47c --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/service/AlbumService.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.service; + +import com.google.cloud.spanner.sample.entities.Album; +import com.google.cloud.spanner.sample.entities.Track; +import com.google.cloud.spanner.sample.mappers.AlbumMapper; +import com.google.cloud.spanner.sample.mappers.TrackMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class AlbumService { + private final AlbumMapper albumMapper; + + private final TrackMapper trackMapper; + + public AlbumService(AlbumMapper albumMapper, TrackMapper trackMapper) { + this.albumMapper = albumMapper; + this.trackMapper = trackMapper; + } + + /** Creates an album and a set of tracks in a read/write transaction. */ + @Transactional + public Album createAlbumAndTracks(Album album, Track... tracks) { + // Saving an album will update the album entity with the generated primary key. + albumMapper.insert(album); + for (Track track : tracks) { + // Set the id that was generated on the Album before saving it. + track.setId(album.getId()); + trackMapper.insert(track); + } + return album; + } +} diff --git a/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/service/SingerService.java b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/service/SingerService.java new file mode 100644 index 000000000..6298bb63e --- /dev/null +++ b/samples/spring-data-mybatis/src/main/java/com/google/cloud/spanner/sample/service/SingerService.java @@ -0,0 +1,67 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample.service; + +import com.google.cloud.spanner.sample.entities.Album; +import com.google.cloud.spanner.sample.entities.Singer; +import com.google.cloud.spanner.sample.mappers.AlbumMapper; +import com.google.cloud.spanner.sample.mappers.SingerMapper; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SingerService { + private final SingerMapper singerRepository; + + private final AlbumMapper albumRepository; + + public SingerService(SingerMapper singerRepository, AlbumMapper albumRepository) { + this.singerRepository = singerRepository; + this.albumRepository = albumRepository; + } + + /** Creates a singer and a list of albums in a read/write transaction. */ + @Transactional + public Singer createSingerAndAlbums(Singer singer, Album... albums) { + // Saving a singer will update the singer entity with the generated primary key. + singerRepository.insert(singer); + for (Album album : albums) { + // Set the singerId that was generated on the Album before saving it. + album.setSingerId(singer.getId()); + albumRepository.insert(album); + } + return singer; + } + + /** + * Searches for all singers that have a last name starting with any of the given prefixes. This + * method uses a read-only transaction. Read-only transactions should be preferred to read/write + * transactions whenever possible, as read-only transactions do not take locks. + */ + @Transactional(readOnly = true) + public List listSingersWithLastNameStartingWith(String... prefixes) { + ImmutableList.Builder result = ImmutableList.builder(); + // This is not the most efficient way to search for this, but the main purpose of this method is + // to show how to use read-only transactions. + for (String prefix : prefixes) { + result.addAll(singerRepository.findSingersByLastNameStartingWith(prefix)); + } + return result.build(); + } +} diff --git a/samples/spring-data-mybatis/src/main/resources/application-cs.properties b/samples/spring-data-mybatis/src/main/resources/application-cs.properties new file mode 100644 index 000000000..cf70836d2 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/resources/application-cs.properties @@ -0,0 +1,9 @@ + +# This profile uses a Cloud Spanner PostgreSQL database. + +spanner.project=my-project +spanner.instance=my-instance +spanner.database=mybatis-sample + +spring.datasource.url=jdbc:cloudspanner:/projects/${spanner.project}/instances/${spanner.instance}/databases/${spanner.database} +spring.datasource.driver-class-name=com.google.cloud.spanner.jdbc.JdbcDriver diff --git a/samples/spring-data-mybatis/src/main/resources/application-pg.properties b/samples/spring-data-mybatis/src/main/resources/application-pg.properties new file mode 100644 index 000000000..0605cd3ab --- /dev/null +++ b/samples/spring-data-mybatis/src/main/resources/application-pg.properties @@ -0,0 +1,7 @@ + +# This profile uses an open-source PostgreSQL database. + +spring.datasource.url=jdbc:postgresql://localhost:5432/mybatis-sample +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.username=postgres +spring.datasource.password=mysecretpassword diff --git a/samples/spring-data-mybatis/src/main/resources/application.properties b/samples/spring-data-mybatis/src/main/resources/application.properties new file mode 100644 index 000000000..a6900a8ef --- /dev/null +++ b/samples/spring-data-mybatis/src/main/resources/application.properties @@ -0,0 +1,13 @@ + +# This application can use both a Cloud Spanner PostgreSQL database or an open-source PostgreSQL +# database. Which database is used is determined by the active profile: +# 1. 'cs' means use Cloud Spanner. +# 2. 'pg' means use open-source PostgreSQL. + +# Activate the Cloud Spanner profile by default. +# Change to 'pg' to activate the PostgreSQL profile. +spring.profiles.default=cs + +# Map column names with an underscore to property names in camel case. +# E.g. column 'full_name' maps to Java property 'fullName'. +mybatis.configuration.map-underscore-to-camel-case=true diff --git a/samples/spring-data-mybatis/src/main/resources/create_schema.sql b/samples/spring-data-mybatis/src/main/resources/create_schema.sql new file mode 100644 index 000000000..60552d3ad --- /dev/null +++ b/samples/spring-data-mybatis/src/main/resources/create_schema.sql @@ -0,0 +1,68 @@ +/* + This script creates the database schema for this sample application. + All lines that start with /* skip_on_open_source_pg */ are skipped when the application is running on a + normal PostgreSQL database. The same lines are executed when the application is running on a Cloud + Spanner database. The script is executed by the DatabaseSeeder class. +*/ + +create sequence if not exists id_generator +/* skip_on_open_source_pg */ bit_reversed_positive +; + +create table if not exists singers ( + id bigint not null primary key default nextval('id_generator'), + first_name varchar, + last_name varchar, + full_name varchar generated always as (CASE WHEN first_name IS NULL THEN last_name + WHEN last_name IS NULL THEN first_name + ELSE first_name || ' ' || last_name END) stored, + active boolean default true, + created_at timestamptz default current_timestamp, + updated_at timestamptz default current_timestamp +); + +create table if not exists albums ( + id bigint not null primary key default nextval('id_generator'), + title varchar not null, + marketing_budget numeric, + release_date date, + cover_picture bytea, + singer_id bigint not null, + created_at timestamptz default current_timestamp, + updated_at timestamptz default current_timestamp, + constraint fk_albums_singers foreign key (singer_id) references singers (id) +); + +create table if not exists tracks ( + id bigint not null, + track_number bigint not null, + title varchar not null, + sample_rate float8 not null, + created_at timestamptz default current_timestamp, + updated_at timestamptz default current_timestamp, + primary key (id, track_number) +) +/* skip_on_open_source_pg */ interleave in parent albums on delete cascade +; + +create table if not exists venues ( + id bigint not null primary key default nextval('id_generator'), + name varchar not null, + description jsonb not null, + created_at timestamptz default current_timestamp, + updated_at timestamptz default current_timestamp +); + +create table if not exists concerts ( + id bigint not null primary key default nextval('id_generator'), + venue_id bigint not null, + singer_id bigint not null, + name varchar not null, + start_time timestamptz not null, + end_time timestamptz not null, + created_at timestamptz default current_timestamp, + updated_at timestamptz default current_timestamp, + constraint fk_concerts_venues foreign key (venue_id) references venues (id), + constraint fk_concerts_singers foreign key (singer_id) references singers (id), + constraint chk_end_time_after_start_time check (end_time > start_time) +); diff --git a/samples/spring-data-mybatis/src/main/resources/drop_schema.sql b/samples/spring-data-mybatis/src/main/resources/drop_schema.sql new file mode 100644 index 000000000..23e7b65d3 --- /dev/null +++ b/samples/spring-data-mybatis/src/main/resources/drop_schema.sql @@ -0,0 +1,5 @@ +drop table if exists concerts; +drop table if exists venues; +drop table if exists tracks; +drop table if exists albums; +drop table if exists singers; diff --git a/samples/spring-data-mybatis/src/test/java/com/google/cloud/spanner/sample/ApplicationTest.java b/samples/spring-data-mybatis/src/test/java/com/google/cloud/spanner/sample/ApplicationTest.java new file mode 100644 index 000000000..677e086ec --- /dev/null +++ b/samples/spring-data-mybatis/src/test/java/com/google/cloud/spanner/sample/ApplicationTest.java @@ -0,0 +1,879 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.sample; + +import static com.google.cloud.spanner.sample.DatabaseSeeder.INITIAL_SINGERS; +import static com.google.cloud.spanner.sample.DatabaseSeeder.randomDate; +import static com.google.cloud.spanner.sample.DatabaseSeeder.randomTitle; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertNotEquals; + +import com.google.cloud.spanner.Dialect; +import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.connection.AbstractMockServerTest; +import com.google.cloud.spanner.sample.entities.Singer; +import com.google.common.collect.Streams; +import com.google.longrunning.Operation; +import com.google.protobuf.Any; +import com.google.protobuf.Empty; +import com.google.protobuf.ListValue; +import com.google.protobuf.NullValue; +import com.google.protobuf.Value; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.v1.BeginTransactionRequest; +import com.google.spanner.v1.CommitRequest; +import com.google.spanner.v1.ExecuteBatchDmlRequest; +import com.google.spanner.v1.ExecuteSqlRequest; +import com.google.spanner.v1.ResultSet; +import com.google.spanner.v1.ResultSetMetadata; +import com.google.spanner.v1.ResultSetStats; +import com.google.spanner.v1.StructType; +import com.google.spanner.v1.StructType.Field; +import com.google.spanner.v1.Type; +import com.google.spanner.v1.TypeAnnotationCode; +import com.google.spanner.v1.TypeCode; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.springframework.boot.SpringApplication; + +@RunWith(JUnit4.class) +public class ApplicationTest extends AbstractMockServerTest { + + @BeforeClass + public static void setupQueryResults() { + // Set the database dialect. + mockSpanner.putStatementResult(StatementResult.detectDialectResult(Dialect.POSTGRESQL)); + // Set up a result for the dialect check that is executed by the JdbcConfiguration class. + mockSpanner.putStatementResult( + StatementResult.query( + Statement.of( + "select 1 " + + "from information_schema.database_options " + + "where schema_name='public' " + + "and option_name='database_dialect' " + + "and option_value='POSTGRESQL'"), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("c") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .build()) + .build()) + .addRows( + ListValue.newBuilder() + .addValues(Value.newBuilder().setStringValue("1").build()) + .build()) + .build())); + // Add a DDL response to the server. + addDdlResponseToSpannerAdmin(); + + // Set up results for the 'delete all test data' operations. + mockSpanner.putStatementResult( + StatementResult.update(Statement.of("delete from concerts"), 0L)); + mockSpanner.putStatementResult(StatementResult.update(Statement.of("delete from venues"), 0L)); + mockSpanner.putStatementResult(StatementResult.update(Statement.of("delete from tracks"), 0L)); + mockSpanner.putStatementResult(StatementResult.update(Statement.of("delete from albums"), 0L)); + mockSpanner.putStatementResult(StatementResult.update(Statement.of("delete from singers"), 0L)); + + // Set up results for inserting test data. + for (Singer singer : INITIAL_SINGERS) { + mockSpanner.putStatementResult( + StatementResult.update( + Statement.newBuilder("insert into singers (first_name, last_name) values ($1, $2)") + .bind("p1") + .to(singer.getFirstName()) + .bind("p2") + .to(singer.getLastName()) + .build(), + 1L)); + } + mockSpanner.putStatementResult( + StatementResult.query( + Statement.of("select id from singers"), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("id") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .build()) + .build()) + .addAllRows( + LongStream.rangeClosed(1L, INITIAL_SINGERS.size()) + .mapToObj( + id -> + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue(String.valueOf(Long.reverse(id))) + .build()) + .build()) + .collect(Collectors.toList())) + .build())); + mockSpanner.putPartialStatementResult( + StatementResult.update( + Statement.of( + "insert into albums (title, marketing_budget, release_date, cover_picture, singer_id) values ($1, $2, $3, $4, $5)"), + 1L)); + + // Set up results for the queries that the application runs. + mockSpanner.putStatementResult( + StatementResult.query( + Statement.of("SELECT * FROM singers ORDER BY last_name, first_name, id"), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("id") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("active") + .setType(Type.newBuilder().setCode(TypeCode.BOOL).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("last_name") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("full_name") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("updated_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("created_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("first_name") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .build()) + .build()) + .addAllRows( + Streams.mapWithIndex( + INITIAL_SINGERS.stream(), + (singer, index) -> + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue(String.valueOf(Long.reverse(index + 1))) + .build()) + .addValues(Value.newBuilder().setBoolValue(true).build()) + .addValues( + Value.newBuilder() + .setStringValue(singer.getLastName()) + .build()) + .addValues( + Value.newBuilder() + .setStringValue( + singer.getFirstName() + " " + singer.getLastName()) + .build()) + .addValues( + Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + Value.newBuilder() + .setStringValue(singer.getFirstName()) + .build()) + .build()) + .collect(Collectors.toList())) + .build())); + mockSpanner.putPartialStatementResult( + StatementResult.query( + Statement.of("SELECT COUNT(1) FROM albums WHERE singer_id = $1"), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("c") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .build()) + .build()) + .addRows( + ListValue.newBuilder() + .addValues(Value.newBuilder().setStringValue("10").build()) + .build()) + .build())); + for (long singerId : LongStream.rangeClosed(1L, INITIAL_SINGERS.size()).toArray()) { + mockSpanner.putStatementResult( + StatementResult.query( + Statement.newBuilder("SELECT * FROM albums WHERE singer_id = $1") + .bind("p1") + .to(Long.reverse(singerId)) + .build(), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("id") + .setType( + Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("title") + .setType( + Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("singer_id") + .setType( + Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("updated_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("created_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("release_date") + .setType(Type.newBuilder().setCode(TypeCode.DATE).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("cover_picture") + .setType( + Type.newBuilder().setCode(TypeCode.BYTES).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("marketing_budget") + .setType( + Type.newBuilder() + .setCode(TypeCode.NUMERIC) + .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) + .build()) + .build()) + .build()) + .build()) + .addAllRows( + IntStream.rangeClosed(1, 10) + .mapToObj( + albumId -> + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue( + String.valueOf(Long.reverse(albumId * singerId))) + .build()) + .addValues(Value.newBuilder().setStringValue(randomTitle())) + .addValues( + Value.newBuilder() + .setStringValue( + String.valueOf(Long.reverse(singerId)))) + .addValues( + Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + Value.newBuilder() + .setStringValue(randomDate().toString()) + .build()) + .addValues( + Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .build()) + .collect(Collectors.toList())) + .build())); + } + mockSpanner.putStatementResult( + StatementResult.query( + Statement.newBuilder( + "INSERT INTO singers (first_name, last_name, active) VALUES ($1, $2, $3)\n" + + "RETURNING *") + .bind("p1") + .to("Amethyst") + .bind("p2") + .to("Jiang") + .bind("p3") + .to((com.google.cloud.spanner.Value) null) + .build(), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("id") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("active") + .setType(Type.newBuilder().setCode(TypeCode.BOOL).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("last_name") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("full_name") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("updated_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("created_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("first_name") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .build()) + .build()) + .addRows( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue( + String.valueOf(Long.reverse(INITIAL_SINGERS.size() + 2))) + .build()) + .addValues(Value.newBuilder().setBoolValue(true).build()) + .addValues(Value.newBuilder().setStringValue("Amethyst").build()) + .addValues(Value.newBuilder().setStringValue("Amethyst Jiang").build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setStringValue("Jiang").build()) + .build()) + .setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build()) + .build())); + mockSpanner.putPartialStatementResult( + StatementResult.query( + Statement.of( + "INSERT INTO albums (title, marketing_budget, release_date, cover_picture, singer_id) VALUES ($1, $2, $3, $4, $5)\n" + + "RETURNING *"), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("id") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("title") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("singer_id") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("updated_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("created_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("release_date") + .setType(Type.newBuilder().setCode(TypeCode.DATE).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("cover_picture") + .setType(Type.newBuilder().setCode(TypeCode.BYTES).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("marketing_budget") + .setType( + Type.newBuilder() + .setCode(TypeCode.NUMERIC) + .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) + .build()) + .build()) + .build()) + .build()) + .addRows( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue(String.valueOf(Long.reverse(1L))) + .build()) + .addValues(Value.newBuilder().setStringValue(randomTitle())) + .addValues(Value.newBuilder().setStringValue(String.valueOf(1L))) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues( + Value.newBuilder().setStringValue(randomDate().toString()).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .build()) + .setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build()) + .build())); + mockSpanner.putPartialStatementResult( + StatementResult.query( + Statement.of( + "INSERT INTO tracks (id, track_number, title, sample_rate) VALUES ($1, $2, $3, $4)\n" + + "RETURNING *"), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("id") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("track_number") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("title") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("sample_rate") + .setType( + Type.newBuilder().setCode(TypeCode.FLOAT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("updated_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("created_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .build()) + .build()) + .addRows( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue(String.valueOf(Long.reverse(1L))) + .build()) + .addValues(Value.newBuilder().setStringValue("1").build()) + .addValues(Value.newBuilder().setStringValue(randomTitle())) + .addValues(Value.newBuilder().setNumberValue(3.14d)) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .build()) + .setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build()) + .build())); + mockSpanner.putStatementResult( + StatementResult.query( + Statement.of("select * from albums limit 1"), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("id") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("title") + .setType(Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("singer_id") + .setType(Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("updated_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("created_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("release_date") + .setType(Type.newBuilder().setCode(TypeCode.DATE).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("cover_picture") + .setType(Type.newBuilder().setCode(TypeCode.BYTES).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("marketing_budget") + .setType( + Type.newBuilder() + .setCode(TypeCode.NUMERIC) + .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC) + .build()) + .build()) + .build()) + .build()) + .addRows( + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue(String.valueOf(Long.reverse(1L))) + .build()) + .addValues(Value.newBuilder().setStringValue(randomTitle())) + .addValues(Value.newBuilder().setStringValue(String.valueOf(1L))) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues( + Value.newBuilder().setStringValue(randomDate().toString()).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()) + .build()) + .setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build()) + .build())); + for (String prefix : new String[] {"J", "A", "B", "C"}) { + mockSpanner.putStatementResult( + StatementResult.query( + Statement.newBuilder("SELECT * FROM singers WHERE starts_with(last_name, $1)") + .bind("p1") + .to(prefix) + .build(), + ResultSet.newBuilder() + .setMetadata( + ResultSetMetadata.newBuilder() + .setRowType( + StructType.newBuilder() + .addFields( + Field.newBuilder() + .setName("id") + .setType( + Type.newBuilder().setCode(TypeCode.INT64).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("active") + .setType(Type.newBuilder().setCode(TypeCode.BOOL).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("last_name") + .setType( + Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("full_name") + .setType( + Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("updated_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("created_at") + .setType( + Type.newBuilder().setCode(TypeCode.TIMESTAMP).build()) + .build()) + .addFields( + Field.newBuilder() + .setName("first_name") + .setType( + Type.newBuilder().setCode(TypeCode.STRING).build()) + .build()) + .build()) + .build()) + .addAllRows( + Streams.mapWithIndex( + INITIAL_SINGERS.stream() + .filter( + singer -> + singer.getLastName().startsWith(prefix.substring(0, 1))), + (singer, index) -> + ListValue.newBuilder() + .addValues( + Value.newBuilder() + .setStringValue( + String.valueOf(Long.reverse(index + 1))) + .build()) + .addValues(Value.newBuilder().setBoolValue(true).build()) + .addValues( + Value.newBuilder() + .setStringValue(singer.getLastName()) + .build()) + .addValues( + Value.newBuilder() + .setStringValue( + singer.getFirstName() + + " " + + singer.getLastName()) + .build()) + .addValues( + Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + Value.newBuilder() + .setNullValue(NullValue.NULL_VALUE) + .build()) + .addValues( + Value.newBuilder() + .setStringValue(singer.getFirstName()) + .build()) + .build()) + .collect(Collectors.toList())) + .build())); + } + } + + @Test + public void testRunApplication() { + System.setProperty("port", String.valueOf(getPort())); + SpringApplication.run(Application.class).close(); + + assertEquals( + 39, + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> !request.getSql().equals("SELECT 1")) + .count()); + assertEquals(3, mockSpanner.countRequestsOfType(ExecuteBatchDmlRequest.class)); + assertEquals(5, mockSpanner.countRequestsOfType(CommitRequest.class)); + + // Verify that the service methods use transactions. + String insertSingerSql = + "INSERT INTO singers (first_name, last_name, active) VALUES ($1, $2, $3)\nRETURNING *"; + assertEquals( + 1, + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(insertSingerSql)) + .count()); + ExecuteSqlRequest insertSingerRequest = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(insertSingerSql)) + .findFirst() + .orElseThrow(); + assertTrue(insertSingerRequest.hasTransaction()); + assertTrue(insertSingerRequest.getTransaction().hasBegin()); + assertTrue(insertSingerRequest.getTransaction().getBegin().hasReadWrite()); + String insertAlbumSql = + "INSERT INTO albums (title, marketing_budget, release_date, cover_picture, singer_id) " + + "VALUES ($1, $2, $3, $4, $5)\nRETURNING *"; + assertEquals( + 4, + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(insertAlbumSql)) + .count()); + // The first 3 requests belong to the transaction that is executed together with the 'INSERT + // INTO singers' statement. + List insertAlbumRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(insertAlbumSql)) + .toList() + .subList(0, 3); + ExecuteSqlRequest firstInsertAlbumRequest = insertAlbumRequests.get(0); + for (ExecuteSqlRequest request : insertAlbumRequests) { + assertTrue(request.hasTransaction()); + assertTrue(request.getTransaction().hasId()); + assertEquals( + firstInsertAlbumRequest.getTransaction().getId(), request.getTransaction().getId()); + } + // Verify that the transaction is committed. + assertEquals( + 1, + mockSpanner.getRequestsOfType(CommitRequest.class).stream() + .filter( + request -> + request + .getTransactionId() + .equals(firstInsertAlbumRequest.getTransaction().getId())) + .count()); + + // The last 'INSERT INTO albums' request belong in a transaction with 8 'INSERT INTO tracks' + // requests. + ExecuteSqlRequest lastInsertAlbumRequest = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(insertAlbumSql)) + .toList() + .get(3); + assertNotEquals( + lastInsertAlbumRequest.getTransaction().getId(), + firstInsertAlbumRequest.getTransaction().getId()); + assertTrue(lastInsertAlbumRequest.hasTransaction()); + assertTrue(lastInsertAlbumRequest.getTransaction().hasBegin()); + assertTrue(lastInsertAlbumRequest.getTransaction().getBegin().hasReadWrite()); + String insertTrackSql = + "INSERT INTO tracks (id, track_number, title, sample_rate) " + + "VALUES ($1, $2, $3, $4)\nRETURNING *"; + assertEquals( + 7, + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(insertTrackSql)) + .count()); + List insertTrackRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(insertTrackSql)) + .toList(); + for (ExecuteSqlRequest request : insertTrackRequests) { + assertTrue(request.hasTransaction()); + assertTrue(request.getTransaction().hasId()); + assertEquals( + insertTrackRequests.get(0).getTransaction().getId(), request.getTransaction().getId()); + } + // Verify that the transaction is committed. + assertEquals( + 1, + mockSpanner.getRequestsOfType(CommitRequest.class).stream() + .filter( + request -> + request + .getTransactionId() + .equals(insertTrackRequests.get(0).getTransaction().getId())) + .count()); + + // Verify that the SingerService#listSingersWithLastNameStartingWith(..) method uses a read-only + // transaction. + assertEquals( + 1, + mockSpanner.getRequestsOfType(BeginTransactionRequest.class).stream() + .filter(request -> request.getOptions().hasReadOnly()) + .count()); + String selectSingersSql = "SELECT * FROM singers WHERE starts_with(last_name, $1)"; + assertEquals( + 4, + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(selectSingersSql)) + .count()); + List selectSingersRequests = + mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream() + .filter(request -> request.getSql().equals(selectSingersSql)) + .toList() + .subList(1, 4); + ExecuteSqlRequest firstSelectSingersRequest = selectSingersRequests.get(0); + for (ExecuteSqlRequest request : selectSingersRequests) { + assertTrue(request.hasTransaction()); + assertTrue(request.getTransaction().hasId()); + } + // Verify that the read-only transaction is not committed. + assertEquals( + 0, + mockSpanner.getRequestsOfType(CommitRequest.class).stream() + .filter( + request -> + request + .getTransactionId() + .equals(firstSelectSingersRequest.getTransaction().getId())) + .count()); + } + + private static void addDdlResponseToSpannerAdmin() { + mockDatabaseAdmin.addResponse( + Operation.newBuilder() + .setDone(true) + .setResponse(Any.pack(Empty.getDefaultInstance())) + .setMetadata(Any.pack(UpdateDatabaseDdlMetadata.getDefaultInstance())) + .build()); + } +} diff --git a/samples/spring-data-mybatis/src/test/resources/application-cs.properties b/samples/spring-data-mybatis/src/test/resources/application-cs.properties new file mode 100644 index 000000000..05f7cfa92 --- /dev/null +++ b/samples/spring-data-mybatis/src/test/resources/application-cs.properties @@ -0,0 +1,9 @@ + +# This profile uses a Cloud Spanner PostgreSQL database. + +spanner.project=my-project +spanner.instance=my-instance +spanner.database=spring-data-jdbc + +spring.datasource.url=jdbc:cloudspanner://localhost:${port}/projects/${spanner.project}/instances/${spanner.instance}/databases/${spanner.database}?usePlainText=true +spring.datasource.driver-class-name=com.google.cloud.spanner.jdbc.JdbcDriver diff --git a/samples/spring-data-mybatis/src/test/resources/application-pg.properties b/samples/spring-data-mybatis/src/test/resources/application-pg.properties new file mode 100644 index 000000000..894f63eba --- /dev/null +++ b/samples/spring-data-mybatis/src/test/resources/application-pg.properties @@ -0,0 +1,7 @@ + +# This profile uses an open-source PostgreSQL database. + +spring.datasource.url=jdbc:postgresql://localhost:5432/spring-data-jdbc +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.username=postgres +spring.datasource.password=mysecretpassword diff --git a/samples/spring-data-mybatis/src/test/resources/application.properties b/samples/spring-data-mybatis/src/test/resources/application.properties new file mode 100644 index 000000000..a6900a8ef --- /dev/null +++ b/samples/spring-data-mybatis/src/test/resources/application.properties @@ -0,0 +1,13 @@ + +# This application can use both a Cloud Spanner PostgreSQL database or an open-source PostgreSQL +# database. Which database is used is determined by the active profile: +# 1. 'cs' means use Cloud Spanner. +# 2. 'pg' means use open-source PostgreSQL. + +# Activate the Cloud Spanner profile by default. +# Change to 'pg' to activate the PostgreSQL profile. +spring.profiles.default=cs + +# Map column names with an underscore to property names in camel case. +# E.g. column 'full_name' maps to Java property 'fullName'. +mybatis.configuration.map-underscore-to-camel-case=true From 749d2c3698c900560b6f85247b0a41a85cd55ac8 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 26 Sep 2023 16:06:13 +0200 Subject: [PATCH 03/10] deps: update dependency org.springframework.boot:spring-boot-starter-parent to v3.1.4 (#1366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [org.springframework.boot:spring-boot-starter-parent](https://spring.io/projects/spring-boot) ([source](https://togithub.com/spring-projects/spring-boot)) | `3.1.3` -> `3.1.4` | [![age](https://developer.mend.io/api/mc/badges/age/maven/org.springframework.boot:spring-boot-starter-parent/3.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.springframework.boot:spring-boot-starter-parent/3.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.springframework.boot:spring-boot-starter-parent/3.1.3/3.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.springframework.boot:spring-boot-starter-parent/3.1.3/3.1.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
spring-projects/spring-boot (org.springframework.boot:spring-boot-starter-parent) ### [`v3.1.4`](https://togithub.com/spring-projects/spring-boot/releases/tag/v3.1.4) [Compare Source](https://togithub.com/spring-projects/spring-boot/compare/v3.1.3...v3.1.4) ##### :star: New Features - Add TWENTY_ONE to JavaVersion enum [#​37364](https://togithub.com/spring-projects/spring-boot/issues/37364) ##### :lady_beetle: Bug Fixes - When SLF4J and Logback are initialized on multiple threads in parallel, startup may fail due to SubstituteLoggerFactory being considered to be a competing LoggerFactory implementation [#​37484](https://togithub.com/spring-projects/spring-boot/issues/37484) - Saml2RelyingPartyAutoConfiguration ignores `sign-request` when `metadata-url` is used [#​37482](https://togithub.com/spring-projects/spring-boot/issues/37482) - Leaking file descriptor / socket within DomainSocket tooling [#​37460](https://togithub.com/spring-projects/spring-boot/issues/37460) - Invalid Accept header produces HTTP 500 in WelcomePageHandlerMapping [#​37457](https://togithub.com/spring-projects/spring-boot/issues/37457) - PrivateKeyParser doesn't support ed448, XDH and RSA-PSS keys [#​37422](https://togithub.com/spring-projects/spring-boot/issues/37422) - "languageVersion is final and cannot be changed" when using Gradle 8.3 and configuring the Java toolchain's language version [#​37380](https://togithub.com/spring-projects/spring-boot/issues/37380) - AOT processing fails when a `@ConfigurationProperties-annotated` record has multiple constructors [#​37336](https://togithub.com/spring-projects/spring-boot/issues/37336) - Spring Boot dependency management not working for ehcache when using Gradle and the dependency management plugin [#​37270](https://togithub.com/spring-projects/spring-boot/issues/37270) - SslStoreBundle implementations aren't immutable [#​37222](https://togithub.com/spring-projects/spring-boot/issues/37222) - Parsing OCI image names that are invalid due to the use of upper case letters is very slow [#​37183](https://togithub.com/spring-projects/spring-boot/issues/37183) - Producing and consuming different tracing propagation formats doesn't work [#​37178](https://togithub.com/spring-projects/spring-boot/issues/37178) - Using https with elliptic curves other than secp384r1 fails [#​37169](https://togithub.com/spring-projects/spring-boot/issues/37169) - In 3.0.x and later, Spring Security cannot be used to secure a WebSocket upgrade request when using Jetty [#​37158](https://togithub.com/spring-projects/spring-boot/issues/37158) - Local baggage is propagated when using Brave and W3C [#​37156](https://togithub.com/spring-projects/spring-boot/issues/37156) - ServiceConnectionContextCustomizer can trigger docker usage during AOT processing [#​37097](https://togithub.com/spring-projects/spring-boot/issues/37097) - java.lang.OutOfMemoryError: Metaspace when repeatedly deploying and undeploying a Spring Boot web application multiple times in Tomcat [#​37096](https://togithub.com/spring-projects/spring-boot/issues/37096) - Property 'logging.threshold.console' not working [#​36741](https://togithub.com/spring-projects/spring-boot/issues/36741) ##### :notebook_with_decorative_cover: Documentation - Document that PKCS8 PEM files should be used whenever possible [#​37443](https://togithub.com/spring-projects/spring-boot/issues/37443) - Add reference to Oracle Spring Boot Starters [#​37411](https://togithub.com/spring-projects/spring-boot/issues/37411) - Correct the description of spring.artemis.broker-url [#​37309](https://togithub.com/spring-projects/spring-boot/issues/37309) - Add default value metadata for management.metrics.export.signalfx.published-histogram-type [#​37253](https://togithub.com/spring-projects/spring-boot/issues/37253) - Polish javadoc [#​37143](https://togithub.com/spring-projects/spring-boot/issues/37143) ##### :hammer: Dependency Upgrades - Upgrade to Byte Buddy 1.14.8 [#​37419](https://togithub.com/spring-projects/spring-boot/issues/37419) - Upgrade to Couchbase Client 3.4.10 [#​37297](https://togithub.com/spring-projects/spring-boot/issues/37297) - Upgrade to Groovy 4.0.15 [#​37386](https://togithub.com/spring-projects/spring-boot/issues/37386) - Upgrade to Hibernate 6.2.9.Final [#​37465](https://togithub.com/spring-projects/spring-boot/issues/37465) - Upgrade to Infinispan 14.0.17.Final [#​37299](https://togithub.com/spring-projects/spring-boot/issues/37299) - Upgrade to Jakarta XML Bind 4.0.1 [#​37387](https://togithub.com/spring-projects/spring-boot/issues/37387) - Upgrade to Jetty 11.0.16 [#​37300](https://togithub.com/spring-projects/spring-boot/issues/37300) - Upgrade to Lombok 1.18.30 [#​37488](https://togithub.com/spring-projects/spring-boot/issues/37488) - Upgrade to Micrometer 1.11.4 [#​37261](https://togithub.com/spring-projects/spring-boot/issues/37261) - Upgrade to Micrometer Tracing 1.1.5 [#​37262](https://togithub.com/spring-projects/spring-boot/issues/37262) - Upgrade to Native Build Tools Plugin 0.9.27 [#​37420](https://togithub.com/spring-projects/spring-boot/issues/37420) - Upgrade to Neo4j Java Driver 5.12.0 [#​37353](https://togithub.com/spring-projects/spring-boot/issues/37353) - Upgrade to Pooled JMS 3.1.3 [#​37421](https://togithub.com/spring-projects/spring-boot/issues/37421) - Upgrade to R2DBC MySQL 1.0.3 [#​37466](https://togithub.com/spring-projects/spring-boot/issues/37466) - Upgrade to Reactor Bom 2022.0.11 [#​37263](https://togithub.com/spring-projects/spring-boot/issues/37263) - Upgrade to REST Assured 5.3.2 [#​37303](https://togithub.com/spring-projects/spring-boot/issues/37303) - Upgrade to SLF4J 2.0.9 [#​37304](https://togithub.com/spring-projects/spring-boot/issues/37304) - Upgrade to Spring AMQP 3.0.9 [#​37264](https://togithub.com/spring-projects/spring-boot/issues/37264) - Upgrade to Spring Data Bom 2023.0.4 [#​37350](https://togithub.com/spring-projects/spring-boot/issues/37350) - Upgrade to Spring Framework 6.0.12 [#​37265](https://togithub.com/spring-projects/spring-boot/issues/37265) - Upgrade to Spring GraphQL 1.2.3 [#​37266](https://togithub.com/spring-projects/spring-boot/issues/37266) - Upgrade to Spring Integration 6.1.3 [#​37267](https://togithub.com/spring-projects/spring-boot/issues/37267) - Upgrade to Spring Kafka 3.0.11 [#​37305](https://togithub.com/spring-projects/spring-boot/issues/37305) - Upgrade to Spring Retry 2.0.3 [#​37280](https://togithub.com/spring-projects/spring-boot/issues/37280) - Upgrade to Spring Security 6.1.4 [#​37424](https://togithub.com/spring-projects/spring-boot/issues/37424) - Upgrade to Spring WS 4.0.6 [#​37425](https://togithub.com/spring-projects/spring-boot/issues/37425) - Upgrade to Tomcat 10.1.13 [#​37306](https://togithub.com/spring-projects/spring-boot/issues/37306) ##### :heart: Contributors Thank you to all the contributors who worked on this release: [@​Eng-Fouad](https://togithub.com/Eng-Fouad), [@​dependabot](https://togithub.com/dependabot)\[bot], [@​izeye](https://togithub.com/izeye), [@​markxnelson](https://togithub.com/markxnelson), [@​mdeinum](https://togithub.com/mdeinum), and [@​quaff](https://togithub.com/quaff)
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/java-spanner-jdbc). --- samples/spring-data-mybatis/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml index 2d9de56fb..e522b1756 100644 --- a/samples/spring-data-mybatis/pom.xml +++ b/samples/spring-data-mybatis/pom.xml @@ -13,7 +13,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.3 + 3.1.4 From 8db200cdae34c5b7e85fbf9ddf989bf4b39f9e90 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 26 Sep 2023 16:08:14 +0200 Subject: [PATCH 04/10] chore(deps): update dependency com.google.cloud:google-cloud-spanner-jdbc to v2.13.2 (#1365) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.cloud:google-cloud-spanner-jdbc](https://togithub.com/googleapis/java-spanner-jdbc) | `2.12.1` -> `2.13.2` | [![age](https://developer.mend.io/api/mc/badges/age/maven/com.google.cloud:google-cloud-spanner-jdbc/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.google.cloud:google-cloud-spanner-jdbc/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.google.cloud:google-cloud-spanner-jdbc/2.12.1/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.google.cloud:google-cloud-spanner-jdbc/2.12.1/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | | [com.google.cloud:google-cloud-spanner-jdbc](https://togithub.com/googleapis/java-spanner-jdbc) | `2.13.1` -> `2.13.2` | [![age](https://developer.mend.io/api/mc/badges/age/maven/com.google.cloud:google-cloud-spanner-jdbc/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.google.cloud:google-cloud-spanner-jdbc/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.google.cloud:google-cloud-spanner-jdbc/2.13.1/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.google.cloud:google-cloud-spanner-jdbc/2.13.1/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
googleapis/java-spanner-jdbc (com.google.cloud:google-cloud-spanner-jdbc) ### [`v2.13.2`](https://togithub.com/googleapis/java-spanner-jdbc/blob/HEAD/CHANGELOG.md#2132-2023-09-26) [Compare Source](https://togithub.com/googleapis/java-spanner-jdbc/compare/v2.13.1...v2.13.2) ##### Dependencies - Update dependency com.google.cloud:google-cloud-shared-dependencies to v3.16.0 ([#​1358](https://togithub.com/googleapis/java-spanner-jdbc/issues/1358)) ([c4c4925](https://togithub.com/googleapis/java-spanner-jdbc/commit/c4c492576d3e6c192a1855e8d6b3474bb2ad0c22)) - Update dependency com.google.cloud:google-cloud-shared-dependencies to v3.16.1 ([#​1363](https://togithub.com/googleapis/java-spanner-jdbc/issues/1363)) ([d574dbb](https://togithub.com/googleapis/java-spanner-jdbc/commit/d574dbb761fa7d0a7d1977844b48b8e4904f1bb0)) - Update dependency com.spotify.fmt:fmt-maven-plugin to v2.21.1 ([#​1359](https://togithub.com/googleapis/java-spanner-jdbc/issues/1359)) ([70af99e](https://togithub.com/googleapis/java-spanner-jdbc/commit/70af99e96451fb0158abb45580eaae09ad0b6210)) ### [`v2.13.1`](https://togithub.com/googleapis/java-spanner-jdbc/blob/HEAD/CHANGELOG.md#2131-2023-09-21) [Compare Source](https://togithub.com/googleapis/java-spanner-jdbc/compare/v2.13.0...v2.13.1) ##### Dependencies - Update dependency org.springframework.boot:spring-boot-starter-data-jdbc to v3.1.4 ([#​1353](https://togithub.com/googleapis/java-spanner-jdbc/issues/1353)) ([88cd905](https://togithub.com/googleapis/java-spanner-jdbc/commit/88cd905bece9c8da7f26b637392e35ab2536edeb)) ### [`v2.13.0`](https://togithub.com/googleapis/java-spanner-jdbc/blob/HEAD/CHANGELOG.md#2130-2023-09-15) [Compare Source](https://togithub.com/googleapis/java-spanner-jdbc/compare/v2.12.1...v2.13.0) ##### Features - Support partitioned queries ([#​1300](https://togithub.com/googleapis/java-spanner-jdbc/issues/1300)) ([c50da41](https://togithub.com/googleapis/java-spanner-jdbc/commit/c50da41e688ff48f8967c0f114f5bac8eaac49f9)) ##### Bug Fixes - Comments should be sent to Spanner for PostgreSQL databases ([#​1331](https://togithub.com/googleapis/java-spanner-jdbc/issues/1331)) ([7c9e781](https://togithub.com/googleapis/java-spanner-jdbc/commit/7c9e781bf45b112266e278e1df1586e56043698e)) ##### Documentation - Create Spring Data JDBC sample ([#​1334](https://togithub.com/googleapis/java-spanner-jdbc/issues/1334)) ([cefea55](https://togithub.com/googleapis/java-spanner-jdbc/commit/cefea55086eb191f71a1a493e046cb136f9f9f87)) ##### Dependencies - Update actions/checkout action to v4 - abandoned ([#​1333](https://togithub.com/googleapis/java-spanner-jdbc/issues/1333)) ([ce82b42](https://togithub.com/googleapis/java-spanner-jdbc/commit/ce82b42d3abb8de0f8b3ee2915c2008673775ea1)) - Update dependency org.springframework.data:spring-data-bom to v2023.0.4 ([#​1347](https://togithub.com/googleapis/java-spanner-jdbc/issues/1347)) ([893f61a](https://togithub.com/googleapis/java-spanner-jdbc/commit/893f61ab04e32c690f1ff9fc813bd2ba6ebca328))
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/java-spanner-jdbc). --- samples/install-without-bom/pom.xml | 2 +- samples/spring-data-jdbc/pom.xml | 2 +- samples/spring-data-mybatis/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index ed2d67e79..015e8b35d 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.13.1 + 2.13.2 diff --git a/samples/spring-data-jdbc/pom.xml b/samples/spring-data-jdbc/pom.xml index 557270816..44d3da890 100644 --- a/samples/spring-data-jdbc/pom.xml +++ b/samples/spring-data-jdbc/pom.xml @@ -48,7 +48,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.13.1 + 2.13.2 org.postgresql diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml index e522b1756..d97a09d24 100644 --- a/samples/spring-data-mybatis/pom.xml +++ b/samples/spring-data-mybatis/pom.xml @@ -58,7 +58,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.12.1 + 2.13.2 org.postgresql From 916ad4a9e07b3afc15e53664f175db9e58f06376 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 26 Sep 2023 16:12:13 +0200 Subject: [PATCH 05/10] deps: update dependency org.springframework.data:spring-data-bom to v2023.0.4 (#1367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [org.springframework.data:spring-data-bom](https://togithub.com/spring-projects/spring-data-bom) | `2023.0.3` -> `2023.0.4` | [![age](https://developer.mend.io/api/mc/badges/age/maven/org.springframework.data:spring-data-bom/2023.0.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.springframework.data:spring-data-bom/2023.0.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.springframework.data:spring-data-bom/2023.0.3/2023.0.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.springframework.data:spring-data-bom/2023.0.3/2023.0.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
spring-projects/spring-data-bom (org.springframework.data:spring-data-bom) ### [`v2023.0.4`](https://togithub.com/spring-projects/spring-data-bom/releases/tag/2023.0.4) [Compare Source](https://togithub.com/spring-projects/spring-data-bom/compare/2023.0.3...2023.0.4) #### :shipit: Participating Modules - [Spring Data BOM 2023.0.4](https://togithub.com/spring-projects/spring-data-bom/releases/tag/2023.0.4) - [Spring Data Build 3.1.4](https://togithub.com/spring-projects/spring-data-build/releases/tag/3.1.4) - [Spring Data Cassandra 4.1.4](https://togithub.com/spring-projects/spring-data-cassandra/releases/tag/4.1.4) - [Spring Data Commons 3.1.4](https://togithub.com/spring-projects/spring-data-commons/releases/tag/3.1.4) - [Spring Data Couchbase 5.1.4](https://togithub.com/spring-projects/spring-data-couchbase/releases/tag/5.1.4) - [Spring Data Elasticsearch 5.1.4](https://togithub.com/spring-projects/spring-data-elasticsearch/releases/tag/5.1.4) - [Spring Data JPA 3.1.4](https://togithub.com/spring-projects/spring-data-jpa/releases/tag/3.1.4) - [Spring Data KeyValue 3.1.4](https://togithub.com/spring-projects/spring-data-keyvalue/releases/tag/3.1.4) - [Spring Data LDAP 3.1.4](https://togithub.com/spring-projects/spring-data-ldap/releases/tag/3.1.4) - [Spring Data MongoDB 4.1.4](https://togithub.com/spring-projects/spring-data-mongodb/releases/tag/4.1.4) - [Spring Data Neo4j 7.1.4](https://togithub.com/spring-projects/spring-data-neo4j/releases/tag/7.1.4) - [Spring Data REST 4.1.4](https://togithub.com/spring-projects/spring-data-rest/releases/tag/4.1.4) - [Spring Data Redis 3.1.4](https://togithub.com/spring-projects/spring-data-redis/releases/tag/3.1.4) - [Spring Data Relational 3.1.4](https://togithub.com/spring-projects/spring-data-relational/releases/tag/3.1.4)
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/java-spanner-jdbc). --- samples/spring-data-mybatis/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml index d97a09d24..68026c1a2 100644 --- a/samples/spring-data-mybatis/pom.xml +++ b/samples/spring-data-mybatis/pom.xml @@ -28,7 +28,7 @@ org.springframework.data spring-data-bom - 2023.0.3 + 2023.0.4 import pom From d59c8f553be17136e91dcd133bee310cff9fcf94 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 26 Sep 2023 17:02:06 +0200 Subject: [PATCH 06/10] chore(deps): update dependency com.google.cloud:libraries-bom to v26.23.0 (#1369) --- samples/spring-data-mybatis/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml index 68026c1a2..a350dc1dd 100644 --- a/samples/spring-data-mybatis/pom.xml +++ b/samples/spring-data-mybatis/pom.xml @@ -35,7 +35,7 @@ com.google.cloud libraries-bom - 26.22.0 + 26.23.0 import pom From 376e1c3ccdd71351a5d6151ce19b9f88df163776 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 26 Sep 2023 17:02:29 +0200 Subject: [PATCH 07/10] deps: update dependency com.google.cloud:google-cloud-spanner-bom to v6.48.0 (#1370) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ee49db7d9..ba8863857 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ com.google.cloud google-cloud-spanner-bom - 6.47.0 + 6.48.0 pom import From bf64add3e9ce8148d2fc3ad010b8abd446208e4f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 26 Sep 2023 17:28:16 +0200 Subject: [PATCH 08/10] deps: update dependency com.spotify.fmt:fmt-maven-plugin to v2.21.1 (#1372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.spotify.fmt:fmt-maven-plugin](https://togithub.com/spotify/fmt-maven-plugin) | `2.20` -> `2.21.1` | [![age](https://developer.mend.io/api/mc/badges/age/maven/com.spotify.fmt:fmt-maven-plugin/2.21.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.spotify.fmt:fmt-maven-plugin/2.21.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.spotify.fmt:fmt-maven-plugin/2.20/2.21.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.spotify.fmt:fmt-maven-plugin/2.20/2.21.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
spotify/fmt-maven-plugin (com.spotify.fmt:fmt-maven-plugin) ### [`v2.21.1`](https://togithub.com/spotify/fmt-maven-plugin/releases/tag/2.21.1) [Compare Source](https://togithub.com/spotify/fmt-maven-plugin/compare/2.21...2.21.1) #### What's Changed - Upgrade dependencies by [@​caesar-ralf](https://togithub.com/caesar-ralf) in [https://togithub.com/spotify/fmt-maven-plugin/pull/180](https://togithub.com/spotify/fmt-maven-plugin/pull/180) **Full Changelog**: https://togithub.com/spotify/fmt-maven-plugin/compare/2.21...2.21.1 ### [`v2.21`](https://togithub.com/spotify/fmt-maven-plugin/releases/tag/2.21) #### What's Changed - Use 2.19 for formatting code in this repo by [@​klaraward](https://togithub.com/klaraward) in [https://togithub.com/spotify/fmt-maven-plugin/pull/154](https://togithub.com/spotify/fmt-maven-plugin/pull/154) - Add metadata for Spotify OSS by [@​klaraward](https://togithub.com/klaraward) in [https://togithub.com/spotify/fmt-maven-plugin/pull/155](https://togithub.com/spotify/fmt-maven-plugin/pull/155) - check release in Central instead of search by [@​hboutemy](https://togithub.com/hboutemy) in [https://togithub.com/spotify/fmt-maven-plugin/pull/156](https://togithub.com/spotify/fmt-maven-plugin/pull/156) - configure for Reproducible Builds by [@​hboutemy](https://togithub.com/hboutemy) in [https://togithub.com/spotify/fmt-maven-plugin/pull/157](https://togithub.com/spotify/fmt-maven-plugin/pull/157) - Allow skipping of sourceDirectory or testSourceDirectory by [@​camac](https://togithub.com/camac) in [https://togithub.com/spotify/fmt-maven-plugin/pull/128](https://togithub.com/spotify/fmt-maven-plugin/pull/128) - \[Snyk] Upgrade org.apache.maven.plugin-tools:maven-plugin-annotations from 3.4 to 3.7.1 by [@​snyk-bot](https://togithub.com/snyk-bot) in [https://togithub.com/spotify/fmt-maven-plugin/pull/162](https://togithub.com/spotify/fmt-maven-plugin/pull/162) - \[Snyk] Upgrade io.norberg:auto-matter from 0.25.1 to 0.26.1 by [@​snyk-bot](https://togithub.com/snyk-bot) in [https://togithub.com/spotify/fmt-maven-plugin/pull/163](https://togithub.com/spotify/fmt-maven-plugin/pull/163) - \[Snyk] Upgrade org.apache.maven:maven-plugin-api from 3.8.4 to 3.8.7 by [@​snyk-bot](https://togithub.com/snyk-bot) in [https://togithub.com/spotify/fmt-maven-plugin/pull/164](https://togithub.com/spotify/fmt-maven-plugin/pull/164) - \[Snyk] Upgrade org.apache.maven.plugin-tools:maven-plugin-annotations from 3.7.1 to 3.8.1 by [@​perploug](https://togithub.com/perploug) in [https://togithub.com/spotify/fmt-maven-plugin/pull/169](https://togithub.com/spotify/fmt-maven-plugin/pull/169) - \[Snyk] Upgrade io.norberg:auto-matter from 0.26.1 to 0.26.2 by [@​perploug](https://togithub.com/perploug) in [https://togithub.com/spotify/fmt-maven-plugin/pull/173](https://togithub.com/spotify/fmt-maven-plugin/pull/173) - \[Snyk] Upgrade org.apache.maven.plugin-tools:maven-plugin-annotations from 3.8.1 to 3.8.2 by [@​snyk-bot](https://togithub.com/snyk-bot) in [https://togithub.com/spotify/fmt-maven-plugin/pull/174](https://togithub.com/spotify/fmt-maven-plugin/pull/174) - Bump org.apache.maven:maven-core from 3.3.9 to 3.8.1 by [@​dependabot](https://togithub.com/dependabot) in [https://togithub.com/spotify/fmt-maven-plugin/pull/177](https://togithub.com/spotify/fmt-maven-plugin/pull/177) - \[Snyk] Upgrade org.apache.maven:maven-plugin-api from 3.8.7 to 3.9.0 by [@​snyk-bot](https://togithub.com/snyk-bot) in [https://togithub.com/spotify/fmt-maven-plugin/pull/167](https://togithub.com/spotify/fmt-maven-plugin/pull/167) - Support java 21 by [@​caesar-ralf](https://togithub.com/caesar-ralf) in [https://togithub.com/spotify/fmt-maven-plugin/pull/179](https://togithub.com/spotify/fmt-maven-plugin/pull/179) #### New Contributors - [@​hboutemy](https://togithub.com/hboutemy) made their first contribution in [https://togithub.com/spotify/fmt-maven-plugin/pull/156](https://togithub.com/spotify/fmt-maven-plugin/pull/156) - [@​caesar-ralf](https://togithub.com/caesar-ralf) made their first contribution in [https://togithub.com/spotify/fmt-maven-plugin/pull/179](https://togithub.com/spotify/fmt-maven-plugin/pull/179) **Full Changelog**: https://togithub.com/spotify/fmt-maven-plugin/compare/2.19.0...2.21
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/java-spanner-jdbc). --- samples/spring-data-mybatis/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml index a350dc1dd..615d6ca27 100644 --- a/samples/spring-data-mybatis/pom.xml +++ b/samples/spring-data-mybatis/pom.xml @@ -98,7 +98,7 @@ com.spotify.fmt fmt-maven-plugin - 2.20 + 2.21.1 From b30e391792f2c2811038b35a065b35104bc614e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 27 Sep 2023 10:48:13 +0200 Subject: [PATCH 09/10] deps: remove specific JDBC version from samples (#1371) - Removes the specific JDBC version from the samples. This is no longer needed, as the BOM now contains a JDBC driver version that is up-to-date enough to work with these samples (they require RETURN_GENERATED_KEYS). - Adds a GitHub Actions workflow for testing the MyBatis sample. --- .../workflows/spring-data-mybatis-sample.yaml | 30 +++++++++++++++++++ samples/spring-data-jdbc/pom.xml | 1 - samples/spring-data-mybatis/pom.xml | 1 - 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/spring-data-mybatis-sample.yaml diff --git a/.github/workflows/spring-data-mybatis-sample.yaml b/.github/workflows/spring-data-mybatis-sample.yaml new file mode 100644 index 000000000..39be0e6e6 --- /dev/null +++ b/.github/workflows/spring-data-mybatis-sample.yaml @@ -0,0 +1,30 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Github action job to test core java library features on +# downstream client libraries before they are released. +on: + pull_request: +name: spring-data-mybatis-sample +jobs: + spring-data-jdbc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 17 + - name: Run tests + run: mvn test + working-directory: samples/spring-data-mybatis diff --git a/samples/spring-data-jdbc/pom.xml b/samples/spring-data-jdbc/pom.xml index 44d3da890..20f858d95 100644 --- a/samples/spring-data-jdbc/pom.xml +++ b/samples/spring-data-jdbc/pom.xml @@ -48,7 +48,6 @@ com.google.cloud google-cloud-spanner-jdbc - 2.13.2 org.postgresql diff --git a/samples/spring-data-mybatis/pom.xml b/samples/spring-data-mybatis/pom.xml index 615d6ca27..07563c648 100644 --- a/samples/spring-data-mybatis/pom.xml +++ b/samples/spring-data-mybatis/pom.xml @@ -58,7 +58,6 @@ com.google.cloud google-cloud-spanner-jdbc - 2.13.2 org.postgresql From 4372838caf5a1c9c8b173e32d373b4d8a25699a1 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:16:14 +0000 Subject: [PATCH 10/10] chore(main): release 2.13.3 (#1368) :robot: I have created a release *beep* *boop* --- ## [2.13.3](https://togithub.com/googleapis/java-spanner-jdbc/compare/v2.13.2...v2.13.3) (2023-09-27) ### Dependencies * Remove specific JDBC version from samples ([#1371](https://togithub.com/googleapis/java-spanner-jdbc/issues/1371)) ([b30e391](https://togithub.com/googleapis/java-spanner-jdbc/commit/b30e391792f2c2811038b35a065b35104bc614e7)) * Update dependency com.google.cloud:google-cloud-spanner-bom to v6.48.0 ([#1370](https://togithub.com/googleapis/java-spanner-jdbc/issues/1370)) ([376e1c3](https://togithub.com/googleapis/java-spanner-jdbc/commit/376e1c3ccdd71351a5d6151ce19b9f88df163776)) * Update dependency com.spotify.fmt:fmt-maven-plugin to v2.21.1 ([#1372](https://togithub.com/googleapis/java-spanner-jdbc/issues/1372)) ([bf64add](https://togithub.com/googleapis/java-spanner-jdbc/commit/bf64add3e9ce8148d2fc3ad010b8abd446208e4f)) * Update dependency org.springframework.boot:spring-boot-starter-parent to v3.1.4 ([#1366](https://togithub.com/googleapis/java-spanner-jdbc/issues/1366)) ([749d2c3](https://togithub.com/googleapis/java-spanner-jdbc/commit/749d2c3698c900560b6f85247b0a41a85cd55ac8)) * Update dependency org.springframework.data:spring-data-bom to v2023.0.4 ([#1367](https://togithub.com/googleapis/java-spanner-jdbc/issues/1367)) ([916ad4a](https://togithub.com/googleapis/java-spanner-jdbc/commit/916ad4a9e07b3afc15e53664f175db9e58f06376)) ### Documentation * Add sample for Spring Data MyBatis ([#1352](https://togithub.com/googleapis/java-spanner-jdbc/issues/1352)) ([ce52d07](https://togithub.com/googleapis/java-spanner-jdbc/commit/ce52d07c308bcde0ed1b0c9f4d3556db2590f722)) --- This PR was generated with [Release Please](https://togithub.com/googleapis/release-please). See [documentation](https://togithub.com/googleapis/release-please#release-please). --- CHANGELOG.md | 16 ++++++++++++++++ pom.xml | 2 +- samples/snapshot/pom.xml | 2 +- versions.txt | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 659f4d353..a6db5acc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [2.13.3](https://github.com/googleapis/java-spanner-jdbc/compare/v2.13.2...v2.13.3) (2023-09-27) + + +### Dependencies + +* Remove specific JDBC version from samples ([#1371](https://github.com/googleapis/java-spanner-jdbc/issues/1371)) ([b30e391](https://github.com/googleapis/java-spanner-jdbc/commit/b30e391792f2c2811038b35a065b35104bc614e7)) +* Update dependency com.google.cloud:google-cloud-spanner-bom to v6.48.0 ([#1370](https://github.com/googleapis/java-spanner-jdbc/issues/1370)) ([376e1c3](https://github.com/googleapis/java-spanner-jdbc/commit/376e1c3ccdd71351a5d6151ce19b9f88df163776)) +* Update dependency com.spotify.fmt:fmt-maven-plugin to v2.21.1 ([#1372](https://github.com/googleapis/java-spanner-jdbc/issues/1372)) ([bf64add](https://github.com/googleapis/java-spanner-jdbc/commit/bf64add3e9ce8148d2fc3ad010b8abd446208e4f)) +* Update dependency org.springframework.boot:spring-boot-starter-parent to v3.1.4 ([#1366](https://github.com/googleapis/java-spanner-jdbc/issues/1366)) ([749d2c3](https://github.com/googleapis/java-spanner-jdbc/commit/749d2c3698c900560b6f85247b0a41a85cd55ac8)) +* Update dependency org.springframework.data:spring-data-bom to v2023.0.4 ([#1367](https://github.com/googleapis/java-spanner-jdbc/issues/1367)) ([916ad4a](https://github.com/googleapis/java-spanner-jdbc/commit/916ad4a9e07b3afc15e53664f175db9e58f06376)) + + +### Documentation + +* Add sample for Spring Data MyBatis ([#1352](https://github.com/googleapis/java-spanner-jdbc/issues/1352)) ([ce52d07](https://github.com/googleapis/java-spanner-jdbc/commit/ce52d07c308bcde0ed1b0c9f4d3556db2590f722)) + ## [2.13.2](https://github.com/googleapis/java-spanner-jdbc/compare/v2.13.1...v2.13.2) (2023-09-26) diff --git a/pom.xml b/pom.xml index ba8863857..ab808ec92 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 google-cloud-spanner-jdbc - 2.13.3-SNAPSHOT + 2.13.3 jar Google Cloud Spanner JDBC https://github.com/googleapis/java-spanner-jdbc diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index ec9a8dd95..83ae791af 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,7 +28,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.13.3-SNAPSHOT + 2.13.3 diff --git a/versions.txt b/versions.txt index 094a46df1..60ce25154 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.13.2:2.13.3-SNAPSHOT +google-cloud-spanner-jdbc:2.13.3:2.13.3