Skip to content

Commit 22a62f8

Browse files
authored
[bq] Improve test coverage
Signed-off-by: Volodymyr Perebykivskyi <vova235@gmail.com>
1 parent fa958e2 commit 22a62f8

File tree

46 files changed

+1809
-522
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1809
-522
lines changed

spring-batch-bigquery/README.adoc

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
= spring-batch-bigquery
22

3-
Spring Batch extension which contains an `ItemWriter` implementation for https://cloud.google.com/bigquery[BigQuery] based on https://github.com/googleapis/java-bigquery[Java BigQuery].
4-
It supports writing https://en.wikipedia.org/wiki/Comma-separated_values[CSV], https://en.wikipedia.org/wiki/JSON[JSON] using https://cloud.google.com/bigquery/docs/batch-loading-data[load jobs].
3+
Spring Batch extension which contains an `ItemWriter` and `ItemReader` implementations for https://cloud.google.com/bigquery[BigQuery].
54

6-
== Configuration of `BigQueryCsvItemWriter`
5+
`ItemWriter` supports next formats (https://cloud.google.com/bigquery/docs/batch-loading-data[load jobs]):
76

8-
Next to the https://docs.spring.io/spring-batch/reference/html/configureJob.html[configuration of Spring Batch] one needs to configure the `BigQueryCsvItemWriter`.
7+
* https://en.wikipedia.org/wiki/Comma-separated_values[CSV]
8+
* https://en.wikipedia.org/wiki/JSON[JSON]
9+
10+
Based on https://github.com/googleapis/java-bigquery[Java BigQuery].
11+
12+
== Example of `BigQueryCsvItemWriter`
913

1014
[source,java]
1115
----
@@ -17,24 +21,25 @@ BigQueryCsvItemWriter<MyDto> bigQueryCsvWriter() {
1721
.setFormatOptions(FormatOptions.csv())
1822
.build();
1923
20-
BigQueryCsvItemWriter<MyDto> writer = new BigQueryCsvItemWriterBuilder<MyDto>()
21-
.bigQuery(mockedBigQuery)
24+
return new BigQueryCsvItemWriterBuilder<MyDto>()
25+
.bigQuery(bigQueryService)
2226
.writeChannelConfig(writeConfiguration)
2327
.build();
2428
}
2529
----
2630

27-
Additional examples could be found in https://github.com/spring-projects/spring-batch-extensions/blob/main/spring-batch-bigquery/src/test/java/org/springframework/batch/extensions/bigquery/writer/builder/[here].
31+
== Example of `BigQueryItemReader`
2832

29-
== Configuration properties
30-
[cols="1,1,4"]
31-
.Properties for an item writer
32-
|===
33-
| Property | Required | Description
33+
[source,java]
34+
----
35+
@Bean
36+
BigQueryItemReader<PersonDto> bigQueryReader() {
37+
return new BigQueryQueryItemReaderBuilder<PersonDto>()
38+
.bigQuery(bigQueryService)
39+
.rowMapper(res -> new PersonDto(res.get("name").getStringValue()))
40+
.query("SELECT p.name FROM persons p")
41+
.build();
42+
}
43+
----
3444

35-
| `bigQuery` | yes | BigQuery object that provided by BigQuery Java Library. Responsible for connection with BigQuery.
36-
| `writeChannelConfig` | yes | BigQuery write channel config provided by BigQuery Java Library. Responsible for configuring data type, data channel, jobs that will be sent to BigQuery.
37-
| `rowMapper` | no | Your own converter that specifies how to convert input CSV / JSON to a byte array.
38-
| `datasetInfo` | no | Your way to customize how to create BigQuery dataset.
39-
| `jobConsumer` | no | Your custom handler for BigQuery Job provided by BigQuery Java Library.
40-
|===
45+
Additional examples could be found in the https://github.com/spring-projects/spring-batch-extensions/tree/main/spring-batch-bigquery/src/test/java/org/springframework/batch/extensions/bigquery[test folder].

spring-batch-bigquery/pom.xml

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
~ See the License for the specific language governing permissions and
1515
~ limitations under the License.
1616
-->
17-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
17+
<project xmlns="http://maven.apache.org/POM/4.0.0"
18+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
1820
<modelVersion>4.0.0</modelVersion>
1921

2022
<parent>
2123
<groupId>org.springframework.boot</groupId>
2224
<artifactId>spring-boot-starter-parent</artifactId>
23-
<version>3.4.5</version>
25+
<version>3.5.0</version>
2426
<relativePath/>
2527
</parent>
2628

@@ -62,11 +64,7 @@
6264
<dependency>
6365
<groupId>com.google.cloud</groupId>
6466
<artifactId>google-cloud-bigquery</artifactId>
65-
<version>2.45.0</version>
66-
</dependency>
67-
<dependency>
68-
<groupId>org.apache.commons</groupId>
69-
<artifactId>commons-lang3</artifactId>
67+
<version>2.51.0</version>
7068
</dependency>
7169
<dependency>
7270
<groupId>org.springframework.batch</groupId>
@@ -84,6 +82,11 @@
8482
<artifactId>junit-jupiter-api</artifactId>
8583
<scope>test</scope>
8684
</dependency>
85+
<dependency>
86+
<groupId>org.junit.jupiter</groupId>
87+
<artifactId>junit-jupiter-params</artifactId>
88+
<scope>test</scope>
89+
</dependency>
8790
<dependency>
8891
<groupId>org.mockito</groupId>
8992
<artifactId>mockito-core</artifactId>
@@ -94,6 +97,18 @@
9497
<artifactId>junit-jupiter</artifactId>
9598
<scope>test</scope>
9699
</dependency>
100+
<dependency>
101+
<groupId>org.wiremock</groupId>
102+
<artifactId>wiremock-standalone</artifactId>
103+
<version>3.13.0</version>
104+
<scope>test</scope>
105+
</dependency>
106+
<dependency>
107+
<groupId>org.slf4j</groupId>
108+
<artifactId>jul-to-slf4j</artifactId>
109+
<scope>test</scope>
110+
</dependency>
111+
97112
</dependencies>
98113

99114
<build>
@@ -123,23 +138,23 @@
123138
</execution>
124139
</executions>
125140
</plugin>
126-
<!-- Runs tests -->
141+
<!-- Run tests -->
127142
<plugin>
128143
<groupId>org.apache.maven.plugins</groupId>
129144
<artifactId>maven-surefire-plugin</artifactId>
130145
<configuration>
131146
<includes>
132-
<!-- Google cloud tests are omitted because they are designed to be run locally -->
147+
<!-- Google Cloud tests are omitted because they are designed to be run locally -->
148+
<!-- BigQuery Docker emulator tests are omitted because it is not stable yet -->
133149
<include>**/unit/**</include>
134-
<include>**/emulator/**</include>
135150
</includes>
136151
</configuration>
137152
</plugin>
138153
<!-- Generates a flattened version of the pom.xml, used instead of the original -->
139154
<plugin>
140155
<groupId>org.codehaus.mojo</groupId>
141156
<artifactId>flatten-maven-plugin</artifactId>
142-
<version>1.6.0</version>
157+
<version>1.7.0</version>
143158
<executions>
144159
<execution>
145160
<id>flatten</id>

spring-batch-bigquery/src/main/java/org/springframework/batch/extensions/bigquery/reader/BigQueryQueryItemReader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -64,7 +64,7 @@ public void setBigQuery(BigQuery bigQuery) {
6464
}
6565

6666
/**
67-
* Row mapper which transforms single BigQuery row into desired type.
67+
* Row mapper which transforms single BigQuery row into a desired type.
6868
*
6969
* @param rowMapper your row mapper
7070
*/

spring-batch-bigquery/src/main/java/org/springframework/batch/extensions/bigquery/reader/builder/BigQueryQueryItemReaderBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,10 +19,10 @@
1919
import com.google.cloud.bigquery.BigQuery;
2020
import com.google.cloud.bigquery.FieldValueList;
2121
import com.google.cloud.bigquery.QueryJobConfiguration;
22-
import org.apache.commons.lang3.StringUtils;
2322
import org.springframework.batch.extensions.bigquery.reader.BigQueryQueryItemReader;
2423
import org.springframework.core.convert.converter.Converter;
2524
import org.springframework.util.Assert;
25+
import org.springframework.util.StringUtils;
2626

2727
/**
2828
* A builder for {@link BigQueryQueryItemReader}.
@@ -103,7 +103,7 @@ public BigQueryQueryItemReader<T> build() {
103103
reader.setRowMapper(this.rowMapper);
104104

105105
if (this.jobConfiguration == null) {
106-
Assert.isTrue(StringUtils.isNotBlank(this.query), "No query provided");
106+
Assert.isTrue(StringUtils.hasText(this.query), "No query provided");
107107
reader.setJobConfiguration(QueryJobConfiguration.newBuilder(this.query).build());
108108
} else {
109109
reader.setJobConfiguration(this.jobConfiguration);

spring-batch-bigquery/src/main/java/org/springframework/batch/extensions/bigquery/writer/BigQueryBaseItemWriter.java

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,21 +16,12 @@
1616

1717
package org.springframework.batch.extensions.bigquery.writer;
1818

19-
import com.google.cloud.bigquery.BigQuery;
20-
import com.google.cloud.bigquery.Dataset;
21-
import com.google.cloud.bigquery.DatasetInfo;
22-
import com.google.cloud.bigquery.FormatOptions;
23-
import com.google.cloud.bigquery.Job;
24-
import com.google.cloud.bigquery.Table;
25-
import com.google.cloud.bigquery.TableDataWriteChannel;
26-
import com.google.cloud.bigquery.TableDefinition;
27-
import com.google.cloud.bigquery.TableId;
28-
import com.google.cloud.bigquery.WriteChannelConfiguration;
29-
import org.apache.commons.lang3.BooleanUtils;
19+
import com.google.cloud.bigquery.*;
3020
import org.apache.commons.logging.Log;
3121
import org.apache.commons.logging.LogFactory;
3222
import org.springframework.batch.item.Chunk;
3323
import org.springframework.batch.item.ItemWriter;
24+
import org.springframework.beans.factory.InitializingBean;
3425
import org.springframework.util.Assert;
3526

3627
import java.io.ByteArrayOutputStream;
@@ -41,7 +32,6 @@
4132
import java.util.Optional;
4233
import java.util.concurrent.atomic.AtomicLong;
4334
import java.util.function.Consumer;
44-
import java.util.function.Supplier;
4535

4636
/**
4737
* Base class that holds shared code for JSON and CSV writers.
@@ -50,7 +40,7 @@
5040
* @author Volodymyr Perebykivskyi
5141
* @since 0.1.0
5242
*/
53-
public abstract class BigQueryBaseItemWriter<T> implements ItemWriter<T> {
43+
public abstract class BigQueryBaseItemWriter<T> implements ItemWriter<T>, InitializingBean {
5444

5545
/** Logger that can be reused */
5646
protected final Log logger = LogFactory.getLog(getClass());
@@ -76,6 +66,7 @@ public abstract class BigQueryBaseItemWriter<T> implements ItemWriter<T> {
7666

7767
private BigQuery bigQuery;
7868

69+
private boolean writeFailed;
7970

8071
/**
8172
* Fetches table from the provided configuration.
@@ -168,18 +159,25 @@ private void doWriteDataToBigQuery(ByteBuffer byteBuffer) throws IOException {
168159
writer.write(byteBuffer);
169160
writeChannel = writer;
170161
}
162+
catch (Exception e) {
163+
writeFailed = true;
164+
logger.error("BigQuery error", e);
165+
throw new BigQueryItemWriterException("Error on write happened", e);
166+
}
171167
finally {
172-
String logMessage = "Write operation submitted: " + bigQueryWriteCounter.incrementAndGet();
173-
174-
if (writeChannel != null) {
175-
logMessage += " -- Job ID: " + writeChannel.getJob().getJobId().getJob();
176-
if (this.jobConsumer != null) {
177-
this.jobConsumer.accept(writeChannel.getJob());
168+
if (!writeFailed) {
169+
String logMessage = "Write operation submitted: " + bigQueryWriteCounter.incrementAndGet();
170+
171+
if (writeChannel != null) {
172+
logMessage += " -- Job ID: " + writeChannel.getJob().getJobId().getJob();
173+
if (this.jobConsumer != null) {
174+
this.jobConsumer.accept(writeChannel.getJob());
175+
}
178176
}
179-
}
180177

181-
if (this.logger.isDebugEnabled()) {
182-
this.logger.debug(logMessage);
178+
if (this.logger.isDebugEnabled()) {
179+
this.logger.debug(logMessage);
180+
}
183181
}
184182
}
185183
}
@@ -194,23 +192,22 @@ private TableDataWriteChannel getWriteChannel() {
194192

195193
/**
196194
* Performs common validation for CSV and JSON types.
197-
*
198-
* @param formatSpecificChecks supplies type-specific validation
199195
*/
200-
protected void baseAfterPropertiesSet(Supplier<Void> formatSpecificChecks) {
196+
@Override
197+
public void afterPropertiesSet() {
201198
Assert.notNull(this.bigQuery, "BigQuery service must be provided");
202199
Assert.notNull(this.writeChannelConfig, "Write channel configuration must be provided");
200+
Assert.notNull(this.writeChannelConfig.getFormat(), "Data format must be provided");
203201

204-
Assert.isTrue(BooleanUtils.isFalse(isBigtable()), "Google BigTable is not supported");
205-
Assert.isTrue(BooleanUtils.isFalse(isGoogleSheets()), "Google Sheets is not supported");
206-
Assert.isTrue(BooleanUtils.isFalse(isDatastore()), "Google Datastore is not supported");
207-
Assert.isTrue(BooleanUtils.isFalse(isParquet()), "Parquet is not supported");
208-
Assert.isTrue(BooleanUtils.isFalse(isOrc()), "Orc is not supported");
209-
Assert.isTrue(BooleanUtils.isFalse(isAvro()), "Avro is not supported");
210-
211-
formatSpecificChecks.get();
202+
Assert.isTrue(!isBigtable(), "Google BigTable is not supported");
203+
Assert.isTrue(!isGoogleSheets(), "Google Sheets is not supported");
204+
Assert.isTrue(!isDatastore(), "Google Datastore is not supported");
205+
Assert.isTrue(!isParquet(), "Parquet is not supported");
206+
Assert.isTrue(!isOrc(), "Orc is not supported");
207+
Assert.isTrue(!isAvro(), "Avro is not supported");
208+
Assert.isTrue(!isIceberg(), "Iceberg is not supported");
212209

213-
Assert.notNull(this.writeChannelConfig.getFormat(), "Data format must be provided");
210+
performFormatSpecificChecks();
214211

215212
String dataset = this.writeChannelConfig.getDestinationTable().getDataset();
216213
if (this.datasetInfo == null) {
@@ -262,6 +259,10 @@ private boolean isDatastore() {
262259
return FormatOptions.datastoreBackup().getType().equals(this.writeChannelConfig.getFormat());
263260
}
264261

262+
private boolean isIceberg() {
263+
return FormatOptions.iceberg().getType().equals(this.writeChannelConfig.getFormat());
264+
}
265+
265266
/**
266267
* Schema can be computed on the BigQuery side during upload,
267268
* so it is good to know when schema is supplied by user manually.
@@ -294,4 +295,9 @@ protected boolean tableHasDefinedSchema(Table table) {
294295
*/
295296
protected abstract List<byte[]> convertObjectsToByteArrays(List<? extends T> items);
296297

298+
/**
299+
* Performs specific checks that are unique to the format.
300+
*/
301+
protected abstract void performFormatSpecificChecks();
302+
297303
}

0 commit comments

Comments
 (0)