Skip to content

Commit 4d5d310

Browse files
committed
#2 - Add DatabaseClient.select().
We now allow typesafe and generic selection of rows.
1 parent 1d5bb96 commit 4d5d310

File tree

5 files changed

+458
-10
lines changed

5 files changed

+458
-10
lines changed

src/main/java/org/springframework/data/jdbc/core/function/DatabaseClient.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,20 @@ interface TypedExecuteSpec<T> extends BindSpec<TypedExecuteSpec<T>> {
201201
*/
202202
interface SelectFromSpec {
203203

204+
/**
205+
* Specify the source {@literal table} to select from.
206+
*
207+
* @param table must not be {@literal null} or empty.
208+
* @return
209+
*/
204210
GenericSelectSpec from(String table);
205211

212+
/**
213+
* Specify the source table to select from to using the {@link Class entity class}.
214+
*
215+
* @param table must not be {@literal null}.
216+
* @return
217+
*/
206218
<T> TypedSelectSpec<T> from(Class<T> table);
207219
}
208220

@@ -220,7 +232,7 @@ interface InsertIntoSpec {
220232
GenericInsertSpec into(String table);
221233

222234
/**
223-
* Specify the target {@link Class} table to insert to using the {@link Class entity class}.
235+
* Specify the target table to insert to using the {@link Class entity class}.
224236
*
225237
* @param table must not be {@literal null}.
226238
* @return
@@ -296,12 +308,26 @@ interface TypedSelectSpec<T> extends SelectSpec<TypedSelectSpec<T>> {
296308
*/
297309
interface SelectSpec<S extends SelectSpec<S>> {
298310

311+
/**
312+
* Configure projected fields.
313+
*
314+
* @param selectedFields must not be {@literal null}.
315+
*/
299316
S project(String... selectedFields);
300317

301-
S where(Object criteriaDefinition);
302318

319+
/**
320+
* Configure {@link Sort}.
321+
*
322+
* @param sort must not be {@literal null}.
323+
*/
303324
S orderBy(Sort sort);
304325

326+
/**
327+
* Configure pagination. Overrides {@link Sort} if the {@link Pageable} contains a {@link Sort} object.
328+
*
329+
* @param page must not be {@literal null}.
330+
*/
305331
S page(Pageable page);
306332
}
307333

src/main/java/org/springframework/data/jdbc/core/function/DefaultDatabaseClient.java

Lines changed: 299 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import java.lang.reflect.Method;
3131
import java.lang.reflect.Proxy;
3232
import java.sql.SQLException;
33+
import java.util.ArrayList;
34+
import java.util.Arrays;
3335
import java.util.Collections;
3436
import java.util.LinkedHashMap;
3537
import java.util.List;
@@ -47,12 +49,17 @@
4749
import org.reactivestreams.Publisher;
4850
import org.springframework.dao.DataAccessException;
4951
import org.springframework.dao.UncategorizedDataAccessException;
52+
import org.springframework.data.domain.Pageable;
53+
import org.springframework.data.domain.Sort;
54+
import org.springframework.data.domain.Sort.NullHandling;
55+
import org.springframework.data.domain.Sort.Order;
5056
import org.springframework.data.jdbc.core.function.connectionfactory.ConnectionProxy;
5157
import org.springframework.data.util.Pair;
5258
import org.springframework.jdbc.core.SqlProvider;
5359
import org.springframework.jdbc.support.SQLExceptionTranslator;
5460
import org.springframework.lang.Nullable;
5561
import org.springframework.util.Assert;
62+
import org.springframework.util.StringUtils;
5663

5764
/**
5865
* Default implementation of {@link DatabaseClient}.
@@ -93,7 +100,7 @@ public SqlSpec execute() {
93100

94101
@Override
95102
public SelectFromSpec select() {
96-
throw new UnsupportedOperationException("Implement me");
103+
return new DefaultSelectFromSpec();
97104
}
98105

99106
@Override
@@ -254,7 +261,8 @@ public GenericExecuteSpec sql(Supplier<String> sqlSupplier) {
254261
}
255262

256263
/**
257-
* Default {@link org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec} implementation.
264+
* Base class for {@link org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec}
265+
* implementations.
258266
*/
259267
@RequiredArgsConstructor
260268
private class GenericExecuteSpecSupport {
@@ -481,6 +489,295 @@ protected DefaultTypedGenericExecuteSpec<T> createInstance(Map<Integer, Optional
481489
}
482490
}
483491

492+
/**
493+
* Default {@link org.springframework.data.jdbc.core.function.DatabaseClient.SelectFromSpec} implementation.
494+
*/
495+
class DefaultSelectFromSpec implements SelectFromSpec {
496+
497+
@Override
498+
public GenericSelectSpec from(String table) {
499+
return new DefaultGenericSelectSpec(table);
500+
}
501+
502+
@Override
503+
public <T> TypedSelectSpec<T> from(Class<T> table) {
504+
return new DefaultTypedSelectSpec<>(table);
505+
}
506+
}
507+
508+
/**
509+
* Base class for {@link org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec}
510+
* implementations.
511+
*/
512+
@RequiredArgsConstructor
513+
private abstract class DefaultSelectSpecSupport {
514+
515+
final String table;
516+
final List<String> projectedFields;
517+
final Sort sort;
518+
final Pageable page;
519+
520+
DefaultSelectSpecSupport(String table) {
521+
522+
Assert.hasText(table, "Table name must not be null!");
523+
524+
this.table = table;
525+
this.projectedFields = Collections.emptyList();
526+
this.sort = Sort.unsorted();
527+
this.page = Pageable.unpaged();
528+
}
529+
530+
public DefaultSelectSpecSupport project(String... selectedFields) {
531+
Assert.notNull(selectedFields, "Projection fields must not be null!");
532+
533+
List<String> projectedFields = new ArrayList<>(this.projectedFields.size() + selectedFields.length);
534+
projectedFields.addAll(this.projectedFields);
535+
projectedFields.addAll(Arrays.asList(selectedFields));
536+
537+
return createInstance(table, projectedFields, sort, page);
538+
}
539+
540+
public DefaultSelectSpecSupport orderBy(Sort sort) {
541+
542+
Assert.notNull(sort, "Sort must not be null!");
543+
544+
return createInstance(table, projectedFields, sort, page);
545+
}
546+
547+
public DefaultSelectSpecSupport page(Pageable page) {
548+
549+
Assert.notNull(page, "Pageable must not be null!");
550+
551+
return createInstance(table, projectedFields, sort, page);
552+
}
553+
554+
StringBuilder getLimitOffset(Pageable pageable) {
555+
return new StringBuilder().append("LIMIT").append(' ').append(page.getPageSize()) //
556+
.append(' ').append("OFFSET").append(' ').append(page.getOffset());
557+
}
558+
559+
StringBuilder getSortClause(Sort sort) {
560+
561+
StringBuilder sortClause = new StringBuilder();
562+
563+
for (Order order : sort) {
564+
565+
if (sortClause.length() != 0) {
566+
sortClause.append(',').append(' ');
567+
}
568+
569+
sortClause.append(order.getProperty()).append(' ').append(order.getDirection().isAscending() ? "ASC" : "DESC");
570+
571+
if (order.getNullHandling() == NullHandling.NULLS_FIRST) {
572+
sortClause.append(' ').append("NULLS FIRST");
573+
}
574+
575+
if (order.getNullHandling() == NullHandling.NULLS_LAST) {
576+
sortClause.append(' ').append("NULLS LAST");
577+
}
578+
}
579+
return sortClause;
580+
}
581+
582+
<R> SqlResult<R> execute(String sql, BiFunction<Row, RowMetadata, R> mappingFunction) {
583+
584+
Function<Connection, Statement> selectFunction = it -> {
585+
586+
if (logger.isDebugEnabled()) {
587+
logger.debug("Executing SQL statement [" + sql + "]");
588+
}
589+
590+
return it.createStatement(sql);
591+
};
592+
593+
Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(selectFunction.apply(it).execute());
594+
595+
return new DefaultSqlResult<>(DefaultDatabaseClient.this, //
596+
sql, //
597+
resultFunction, //
598+
it -> Mono.error(new UnsupportedOperationException("Not available for SELECT")), //
599+
mappingFunction);
600+
}
601+
602+
protected abstract DefaultSelectSpecSupport createInstance(String table, List<String> projectedFields, Sort sort,
603+
Pageable page);
604+
}
605+
606+
private class DefaultGenericSelectSpec extends DefaultSelectSpecSupport implements GenericSelectSpec {
607+
608+
public DefaultGenericSelectSpec(String table, List<String> projectedFields, Sort sort, Pageable page) {
609+
super(table, projectedFields, sort, page);
610+
}
611+
612+
DefaultGenericSelectSpec(String table) {
613+
super(table);
614+
}
615+
616+
@Override
617+
public <R> TypedSelectSpec<R> as(Class<R> resultType) {
618+
return new DefaultTypedSelectSpec<>(table, projectedFields, sort, page, resultType,
619+
dataAccessStrategy.getRowMapper(resultType));
620+
}
621+
622+
@Override
623+
public DefaultGenericSelectSpec project(String... selectedFields) {
624+
return (DefaultGenericSelectSpec) super.project(selectedFields);
625+
}
626+
627+
@Override
628+
public DefaultGenericSelectSpec orderBy(Sort sort) {
629+
return (DefaultGenericSelectSpec) super.orderBy(sort);
630+
}
631+
632+
@Override
633+
public DefaultGenericSelectSpec page(Pageable page) {
634+
return (DefaultGenericSelectSpec) super.page(page);
635+
}
636+
637+
@Override
638+
public FetchSpec<Map<String, Object>> fetch() {
639+
return exchange(ColumnMapRowMapper.INSTANCE);
640+
}
641+
642+
@Override
643+
public Mono<SqlResult<Map<String, Object>>> exchange() {
644+
return Mono.just(exchange(ColumnMapRowMapper.INSTANCE));
645+
}
646+
647+
private <R> SqlResult<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunction) {
648+
649+
List<String> projectedFields;
650+
651+
if (this.projectedFields.isEmpty()) {
652+
projectedFields = Collections.singletonList("*");
653+
} else {
654+
projectedFields = this.projectedFields;
655+
}
656+
657+
StringBuilder selectBuilder = new StringBuilder();
658+
selectBuilder.append("SELECT").append(' ') //
659+
.append(StringUtils.collectionToDelimitedString(projectedFields, ", ")).append(' ') //
660+
.append("FROM").append(' ').append(table);
661+
662+
if (sort.isSorted()) {
663+
selectBuilder.append(' ').append("ORDER BY").append(' ').append(getSortClause(sort));
664+
}
665+
666+
if (page.isPaged()) {
667+
selectBuilder.append(' ').append(getLimitOffset(page));
668+
}
669+
670+
return execute(selectBuilder.toString(), mappingFunction);
671+
}
672+
673+
@Override
674+
protected DefaultGenericSelectSpec createInstance(String table, List<String> projectedFields, Sort sort,
675+
Pageable page) {
676+
return new DefaultGenericSelectSpec(table, projectedFields, sort, page);
677+
}
678+
}
679+
680+
/**
681+
* Default implementation of {@link org.springframework.data.jdbc.core.function.DatabaseClient.TypedInsertSpec}.
682+
*/
683+
@SuppressWarnings("unchecked")
684+
private class DefaultTypedSelectSpec<T> extends DefaultSelectSpecSupport implements TypedSelectSpec<T> {
685+
686+
private final Class<?> typeToRead;
687+
private final BiFunction<Row, RowMetadata, T> mappingFunction;
688+
689+
DefaultTypedSelectSpec(Class<T> typeToRead) {
690+
691+
super(dataAccessStrategy.getTableName(typeToRead));
692+
693+
this.typeToRead = typeToRead;
694+
this.mappingFunction = dataAccessStrategy.getRowMapper(typeToRead);
695+
}
696+
697+
DefaultTypedSelectSpec(String table, List<String> projectedFields, Sort sort, Pageable page, Class<?> typeToRead,
698+
BiFunction<Row, RowMetadata, T> mappingFunction) {
699+
super(table, projectedFields, sort, page);
700+
this.typeToRead = typeToRead;
701+
this.mappingFunction = mappingFunction;
702+
}
703+
704+
@Override
705+
public <R> TypedSelectSpec<R> as(Class<R> resultType) {
706+
707+
Assert.notNull(resultType, "Result type must not be null!");
708+
709+
return new DefaultTypedSelectSpec<>(table, projectedFields, sort, page, typeToRead,
710+
dataAccessStrategy.getRowMapper(resultType));
711+
}
712+
713+
@Override
714+
public <R> TypedSelectSpec<R> extract(BiFunction<Row, RowMetadata, R> mappingFunction) {
715+
716+
Assert.notNull(mappingFunction, "Mapping function must not be null!");
717+
718+
return new DefaultTypedSelectSpec<>(table, projectedFields, sort, page, typeToRead, mappingFunction);
719+
}
720+
721+
@Override
722+
public DefaultTypedSelectSpec<T> project(String... selectedFields) {
723+
return (DefaultTypedSelectSpec<T>) super.project(selectedFields);
724+
}
725+
726+
@Override
727+
public DefaultTypedSelectSpec<T> orderBy(Sort sort) {
728+
return (DefaultTypedSelectSpec<T>) super.orderBy(sort);
729+
}
730+
731+
@Override
732+
public DefaultTypedSelectSpec<T> page(Pageable page) {
733+
return (DefaultTypedSelectSpec<T>) super.page(page);
734+
}
735+
736+
@Override
737+
public FetchSpec<T> fetch() {
738+
return exchange(mappingFunction);
739+
}
740+
741+
@Override
742+
public Mono<SqlResult<T>> exchange() {
743+
return Mono.just(exchange(mappingFunction));
744+
}
745+
746+
private <R> SqlResult<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunction) {
747+
748+
List<String> projectedFields;
749+
750+
if (this.projectedFields.isEmpty()) {
751+
projectedFields = dataAccessStrategy.getAllFields(typeToRead);
752+
} else {
753+
projectedFields = this.projectedFields;
754+
}
755+
756+
StringBuilder selectBuilder = new StringBuilder();
757+
selectBuilder.append("SELECT").append(' ') //
758+
.append(StringUtils.collectionToDelimitedString(projectedFields, ", ")).append(' ') //
759+
.append("FROM").append(' ').append(table);
760+
761+
if (sort.isSorted()) {
762+
763+
Sort mappedSort = dataAccessStrategy.getMappedSort(typeToRead, sort);
764+
selectBuilder.append(' ').append("ORDER BY").append(' ').append(getSortClause(mappedSort));
765+
}
766+
767+
if (page.isPaged()) {
768+
selectBuilder.append(' ').append(getLimitOffset(page));
769+
}
770+
771+
return execute(selectBuilder.toString(), mappingFunction);
772+
}
773+
774+
@Override
775+
protected DefaultTypedSelectSpec<T> createInstance(String table, List<String> projectedFields, Sort sort,
776+
Pageable page) {
777+
return new DefaultTypedSelectSpec<>(table, projectedFields, sort, page, typeToRead, mappingFunction);
778+
}
779+
}
780+
484781
/**
485782
* Default {@link org.springframework.data.jdbc.core.function.DatabaseClient.InsertIntoSpec} implementation.
486783
*/

0 commit comments

Comments
 (0)