diff --git a/4.0_TODO b/4.0_TODO index 0dd080d902..3d1da39217 100644 --- a/4.0_TODO +++ b/4.0_TODO @@ -6,3 +6,4 @@ * Require @HStore annotation for HSTORE mapping / binding (remove legacy support for unqualified maps in Postgres plugin) * Throwables throwOnlyException only exists because of the above issue, so clean that up too +* BeanMapper should throw if it can't find a nested type rather than default to `rs.getObject` (strictColumnMapping) diff --git a/core/src/main/java/org/jdbi/v3/core/argument/BeanPropertyArguments.java b/core/src/main/java/org/jdbi/v3/core/argument/BeanPropertyArguments.java index 9167f12a96..ba6b70fece 100644 --- a/core/src/main/java/org/jdbi/v3/core/argument/BeanPropertyArguments.java +++ b/core/src/main/java/org/jdbi/v3/core/argument/BeanPropertyArguments.java @@ -24,7 +24,10 @@ /** * Inspect a {@link java.beans} style object and bind parameters * based on each of its discovered properties. + * + * @deprecated this should never have been public API */ +@Deprecated public class BeanPropertyArguments extends MethodReturnValueNamedArgumentFinder { private final PojoProperties properties; @@ -37,6 +40,15 @@ public BeanPropertyArguments(String prefix, Object bean) { properties = BeanPropertiesFactory.propertiesFor(obj.getClass()); } + /** + * @param prefix an optional prefix (we insert a '.' as a separator) + * @param bean the bean to inspect and bind + */ + protected BeanPropertyArguments(String prefix, Object bean, PojoProperties properties) { + super(prefix, bean); + this.properties = properties; + } + @Override protected Optional getValue(String name, StatementContext ctx) { @SuppressWarnings("unchecked") diff --git a/core/src/main/java/org/jdbi/v3/core/argument/internal/PojoPropertyArguments.java b/core/src/main/java/org/jdbi/v3/core/argument/internal/PojoPropertyArguments.java index dabe817d87..24cfc826b2 100644 --- a/core/src/main/java/org/jdbi/v3/core/argument/internal/PojoPropertyArguments.java +++ b/core/src/main/java/org/jdbi/v3/core/argument/internal/PojoPropertyArguments.java @@ -13,57 +13,26 @@ */ package org.jdbi.v3.core.argument.internal; -import java.util.Map; -import java.util.Optional; - +import org.jdbi.v3.core.argument.BeanPropertyArguments; import org.jdbi.v3.core.argument.NamedArgumentFinder; -import org.jdbi.v3.core.mapper.RowMapper; -import org.jdbi.v3.core.mapper.reflect.BeanMapper; -import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties.PojoProperty; +import org.jdbi.v3.core.mapper.reflect.internal.PojoPropertiesFactories; import org.jdbi.v3.core.statement.StatementContext; -import org.jdbi.v3.core.statement.UnableToCreateStatementException; /** - * Inspect a object and bind parameters via {@link BeanMapper}'s properties. + * This class only exists to use the protected BeanPropertyArguments constructor. + * When we can remove that class from public API, this class will easily merge into it. */ -public class PojoPropertyArguments extends MethodReturnValueNamedArgumentFinder { - private final Map> properties; +@SuppressWarnings("deprecation") +public class PojoPropertyArguments extends BeanPropertyArguments { private final StatementContext ctx; - /** - * @param prefix an optional prefix (we insert a '.' as a separator) - * @param bean the bean to inspect and bind - * @param ctx the statement context - */ public PojoPropertyArguments(String prefix, Object bean, StatementContext ctx) { - super(prefix, bean); + super(prefix, bean, ctx.getConfig(PojoPropertiesFactories.class).propertiesOf(bean.getClass())); this.ctx = ctx; - final RowMapper mapper = ctx.findRowMapperFor(bean.getClass()) - .orElseThrow(() -> new UnableToCreateStatementException("Couldn't find registered property mapper for " + bean.getClass())); - if (!(mapper instanceof BeanMapper)) { - throw new UnableToCreateStatementException("Registered mapper for " + bean.getClass() + " is not a property based mapper"); - } - properties = ((BeanMapper) mapper).getBeanInfo().getProperties(); - } - - @Override - protected Optional getValue(String name, StatementContext ctx2) { - @SuppressWarnings("unchecked") - PojoProperty property = (PojoProperty) properties.get(name); - if (property == null) { - return Optional.empty(); - } - - return Optional.of(new TypedValue(property.getQualifiedType(), property.get(obj))); } @Override protected NamedArgumentFinder getNestedArgumentFinder(Object o) { return new PojoPropertyArguments(null, o, ctx); } - - @Override - public String toString() { - return "{lazy bean property arguments \"" + obj + "\"}"; - } } diff --git a/core/src/main/java/org/jdbi/v3/core/mapper/immutables/ImmutablesPlugin.java b/core/src/main/java/org/jdbi/v3/core/mapper/immutables/ImmutablesPlugin.java new file mode 100644 index 0000000000..fd1784e553 --- /dev/null +++ b/core/src/main/java/org/jdbi/v3/core/mapper/immutables/ImmutablesPlugin.java @@ -0,0 +1,144 @@ +/* + * 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 org.jdbi.v3.core.mapper.immutables; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Type; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.core.internal.JdbiOptionals; +import org.jdbi.v3.core.internal.exceptions.Unchecked; +import org.jdbi.v3.core.mapper.immutables.internal.ImmutablesMapperFactory; +import org.jdbi.v3.core.mapper.reflect.internal.ImmutablesPropertiesFactory; +import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties; +import org.jdbi.v3.core.mapper.reflect.internal.PojoPropertiesFactories; +import org.jdbi.v3.core.spi.JdbiPlugin; +import org.jdbi.v3.meta.Beta; + +/** + * Install support for an Immutables generated {@code Immutable} or {@code Modifiable} value type. + * Note that unlike most plugins, this plugin is expected to be installed multiple times, once for each value type you wish to handle. + */ +@Beta +public class ImmutablesPlugin implements JdbiPlugin { + private final Class spec; + private final Class impl; + private final Function> properties; + + private ImmutablesPlugin(Class spec, Class impl, Function> properties) { + this.spec = spec; + this.impl = impl; + this.properties = properties; + } + + /** + * Register bean arguments and row mapping for an {@code Immutable*} value class, expecting the default generated class and builder names. + * @param spec the specification interface or abstract class + * @param the specification class + * @return a plugin that configures type mapping for the given class + */ + public static ImmutablesPlugin forImmutable(Class spec) { + final Class impl = classByPrefix("Immutable", spec); + return forImmutable(spec, impl, JdbiOptionals.findFirstPresent( + () -> nullaryMethodOf(spec, "builder"), + () -> nullaryMethodOf(impl, "builder")) + .orElseThrow(() -> new IllegalArgumentException("Neither " + spec + " nor " + impl + " have a 'builder' method"))); + } + + /** + * Register bean arguments and row mapping for an {@code Immutable*} value class, using a supplied implementation and builder. + * @param spec the specification interface or abstract class + * @param impl the generated implementation class + * @param builder a supplier of new Builder instances + * @param the specification class + * @param the implementation class + * @return a plugin that configures type mapping for the given class + */ + public static ImmutablesPlugin forImmutable(Class spec, Class impl, Supplier builder) { + return new ImmutablesPlugin(spec, impl, ImmutablesPropertiesFactory.immutable(spec, builder)); + } + + /** + * Register bean arguments and row mapping for an {@code Modifiable*} value class, expecting the default generated class and public nullary constructor. + * @param spec the specification interface or abstract class + * @param the specification class + * @return a plugin that configures type mapping for the given class + */ + public static ImmutablesPlugin forModifiable(Class spec) { + final Class impl = classByPrefix("Modifiable", spec); + return forModifiable(spec, impl, + nullaryMethodOf(impl, "create") + .orElseGet(() -> constructorOf(impl))); + } + + /** + * Register bean arguments and row mapping for an {@code Modifiable*} value class, using a supplied implementation and constructor. + * @param spec the specification interface or abstract class + * @param impl the modifiable class + * @param constructor a supplier of new Modifiable instances + * @param the specification class + * @param the modifiable class + * @return a plugin that configures type mapping for the given class + */ + public static ImmutablesPlugin forModifiable(Class spec, Class impl, Supplier constructor) { + return new ImmutablesPlugin(spec, impl, ImmutablesPropertiesFactory.modifiable(spec, () -> impl.cast(constructor.get()))); + } + + private static Optional> nullaryMethodOf(Class impl, String methodName) { + try { + return Optional.of(Unchecked.supplier(MethodHandles.lookup() + .unreflect(impl.getMethod(methodName))::invoke)); + } catch (ReflectiveOperationException e) { + return Optional.empty(); + } + } + + @SuppressWarnings("unchecked") + private static Supplier constructorOf(Class impl) { + try { + return (Supplier) Unchecked.supplier(MethodHandles.lookup().findConstructor(impl, MethodType.methodType(void.class))::invoke); + } catch (ReflectiveOperationException e) { + throw new IllegalArgumentException("Couldn't find public constructor of " + impl, e); + } + } + + private static Class classByPrefix(String prefix, Class spec) { + final String implName = spec.getPackage().getName() + '.' + prefix + spec.getSimpleName(); + try { + return Class.forName(implName).asSubclass(spec); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Couldn't locate default implementation class " + implName, e); + } + } + + @Override + public void customizeJdbi(Jdbi jdbi) { + jdbi.getConfig(PojoPropertiesFactories.class).register(new Factory()); + jdbi.registerRowMapper(new ImmutablesMapperFactory(spec, impl, properties)); + } + + class Factory implements Function>> { + @Override + public Optional> apply(Type t) { + if (t == spec || t == impl) { + return Optional.of(properties.apply(t)); + } + return Optional.empty(); + } + } +} diff --git a/core/src/main/java/org/jdbi/v3/core/mapper/reflect/ImmutablesMapperFactory.java b/core/src/main/java/org/jdbi/v3/core/mapper/immutables/internal/ImmutablesMapperFactory.java similarity index 59% rename from core/src/main/java/org/jdbi/v3/core/mapper/reflect/ImmutablesMapperFactory.java rename to core/src/main/java/org/jdbi/v3/core/mapper/immutables/internal/ImmutablesMapperFactory.java index a1c9d6a3a5..4f663eb413 100644 --- a/core/src/main/java/org/jdbi/v3/core/mapper/reflect/ImmutablesMapperFactory.java +++ b/core/src/main/java/org/jdbi/v3/core/mapper/immutables/internal/ImmutablesMapperFactory.java @@ -11,48 +11,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jdbi.v3.core.mapper.reflect; +package org.jdbi.v3.core.mapper.immutables.internal; import java.lang.reflect.Type; import java.util.Optional; import java.util.function.Function; -import java.util.function.Supplier; - import org.jdbi.v3.core.config.ConfigRegistry; import org.jdbi.v3.core.generic.GenericTypes; import org.jdbi.v3.core.mapper.RowMapper; import org.jdbi.v3.core.mapper.RowMapperFactory; -import org.jdbi.v3.core.mapper.reflect.internal.ImmutablesPropertiesFactory; +import org.jdbi.v3.core.mapper.reflect.internal.PojoMapper; import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties; -import org.jdbi.v3.meta.Beta; -@Beta +/** + * Row mapper that inspects an {@code immutables}-style Immutable or Modifiable value class for properties + * and binds them in the style of {@link org.jdbi.v3.core.mapper.reflect.BeanMapper}. + * @param the mapped value type + */ public class ImmutablesMapperFactory implements RowMapperFactory { private final Class defn; private final Class impl; - private final Function> properties; + private final Function> properties; - private ImmutablesMapperFactory(Class defn, Class impl, Function> properties) { + public ImmutablesMapperFactory(Class defn, Class impl, Function> properties) { this.defn = defn; this.impl = impl; this.properties = properties; } - public static RowMapperFactory mapImmutable(Class defn, Class immutable, Supplier builder) { - return new ImmutablesMapperFactory<>(defn, immutable, ImmutablesPropertiesFactory.immutable(defn, builder)); - } - - public static RowMapperFactory mapModifiable(Class defn, Class modifiable, Supplier constructor) { - return new ImmutablesMapperFactory<>(defn, modifiable, ImmutablesPropertiesFactory.modifiable(defn, constructor)); - } - @SuppressWarnings("unchecked") @Override public Optional> build(Type type, ConfigRegistry config) { Class erasedType = GenericTypes.getErasedType(type); if (defn.equals(erasedType) || impl.equals(erasedType)) { - return Optional.of(PropertiesMapper.of((Class) erasedType, properties.apply(type))); + return Optional.of(new PojoMapper<>((Class) erasedType, properties.apply(type), "")); } return Optional.empty(); } diff --git a/core/src/main/java/org/jdbi/v3/core/mapper/reflect/BeanMapper.java b/core/src/main/java/org/jdbi/v3/core/mapper/reflect/BeanMapper.java index df0f295a4d..81c368cf07 100644 --- a/core/src/main/java/org/jdbi/v3/core/mapper/reflect/BeanMapper.java +++ b/core/src/main/java/org/jdbi/v3/core/mapper/reflect/BeanMapper.java @@ -24,6 +24,7 @@ import org.jdbi.v3.core.generic.GenericTypes; import org.jdbi.v3.core.mapper.ColumnMapper; import org.jdbi.v3.core.mapper.Nested; +import org.jdbi.v3.core.mapper.NoSuchMapperException; import org.jdbi.v3.core.mapper.RowMapper; import org.jdbi.v3.core.mapper.RowMapperFactory; import org.jdbi.v3.core.mapper.SingleColumnMapper; @@ -32,8 +33,6 @@ import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties.PojoBuilder; import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties.PojoProperty; import org.jdbi.v3.core.statement.StatementContext; -import org.jdbi.v3.meta.Beta; - import static org.jdbi.v3.core.mapper.reflect.ReflectionMapperUtil.anyColumnsStartWithPrefix; import static org.jdbi.v3.core.mapper.reflect.ReflectionMapperUtil.findColumnIndex; import static org.jdbi.v3.core.mapper.reflect.ReflectionMapperUtil.getColumnNames; @@ -46,7 +45,10 @@ * properties. * * The mapped class must have a default constructor. + * + * @deprecated this class should not be public API, use {@link org.jdbi.v3.core.statement.SqlStatement#bindBean(Object)} instead. */ +@Deprecated public class BeanMapper implements RowMapper { static final String DEFAULT_PREFIX = ""; @@ -100,6 +102,7 @@ public static RowMapper of(Class type, String prefix) { return new BeanMapper<>(type, prefix); } + protected boolean strictColumnMapping; // this should be default (only?) behavior but that's a breaking change protected final Class type; protected final String prefix; private final PojoProperties properties; @@ -110,7 +113,7 @@ public static RowMapper of(Class type, String prefix) { this(type, (PojoProperties) BeanPropertiesFactory.propertiesFor(type), prefix); } - BeanMapper(Class type, PojoProperties properties, String prefix) { + protected BeanMapper(Class type, PojoProperties properties, String prefix) { this.type = type; this.properties = properties; this.prefix = prefix.toLowerCase(); @@ -198,15 +201,14 @@ private Optional> specialize0(StatementContext ctx, }); } - ColumnMapper defaultColumnMapper(PojoProperty property) { + private ColumnMapper defaultColumnMapper(PojoProperty property) { + if (strictColumnMapping) { + throw new NoSuchMapperException(String.format( + "Couldn't find mapper for property '%s' of type '%s' from %s", property.getName(), property.getQualifiedType(), type)); + } return (r, n, c) -> r.getObject(n); } - @Beta - public PojoProperties getBeanInfo() { - return properties; - } - private String getName(PojoProperty property) { return property.getAnnotation(ColumnName.class) .map(ColumnName::value) diff --git a/core/src/main/java/org/jdbi/v3/core/mapper/reflect/PropertiesMapper.java b/core/src/main/java/org/jdbi/v3/core/mapper/reflect/PropertiesMapper.java deleted file mode 100644 index 222b0b7274..0000000000 --- a/core/src/main/java/org/jdbi/v3/core/mapper/reflect/PropertiesMapper.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 org.jdbi.v3.core.mapper.reflect; - -import org.jdbi.v3.core.mapper.ColumnMapper; -import org.jdbi.v3.core.mapper.NoSuchMapperException; -import org.jdbi.v3.core.mapper.RowMapper; -import org.jdbi.v3.core.mapper.RowMapperFactory; -import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties; -import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties.PojoProperty; - -class PropertiesMapper extends BeanMapper { - private PropertiesMapper(Class type, PojoProperties properties, String prefix) { - super(type, properties, prefix); - } - - public static RowMapperFactory factory(Class type, PojoProperties properties) { - return RowMapperFactory.of(type, PropertiesMapper.of(type, properties)); - } - - public static RowMapperFactory factory(Class type, PojoProperties properties, String prefix) { - return RowMapperFactory.of(type, PropertiesMapper.of(type, properties, prefix)); - } - - public static RowMapper of(Class type, PojoProperties properties) { - return PropertiesMapper.of(type, properties, DEFAULT_PREFIX); - } - - public static RowMapper of(Class type, PojoProperties properties, String prefix) { - return new PropertiesMapper<>(type, properties, prefix); - } - - @Override - protected ColumnMapper defaultColumnMapper(PojoProperty property) { - throw new NoSuchMapperException(String.format( - "Couldn't find mapper for property '%s' of type '%s' from %s", property.getName(), property.getQualifiedType(), type)); - } -} diff --git a/core/src/main/java/org/jdbi/v3/core/mapper/reflect/internal/PojoMapper.java b/core/src/main/java/org/jdbi/v3/core/mapper/reflect/internal/PojoMapper.java new file mode 100644 index 0000000000..2a0c9a5590 --- /dev/null +++ b/core/src/main/java/org/jdbi/v3/core/mapper/reflect/internal/PojoMapper.java @@ -0,0 +1,25 @@ +/* + * 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 org.jdbi.v3.core.mapper.reflect.internal; + +import org.jdbi.v3.core.mapper.reflect.BeanMapper; + +/** This class only exists to extend BeanMapper with API that would otherwise be unavoidably public. */ +@SuppressWarnings("deprecation") +public class PojoMapper extends BeanMapper { + public PojoMapper(Class type, PojoProperties properties, String prefix) { + super(type, properties, prefix); + strictColumnMapping = true; + } +} diff --git a/core/src/main/java/org/jdbi/v3/core/mapper/reflect/internal/PojoPropertiesFactories.java b/core/src/main/java/org/jdbi/v3/core/mapper/reflect/internal/PojoPropertiesFactories.java new file mode 100644 index 0000000000..f5ff037400 --- /dev/null +++ b/core/src/main/java/org/jdbi/v3/core/mapper/reflect/internal/PojoPropertiesFactories.java @@ -0,0 +1,50 @@ +/* + * 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 org.jdbi.v3.core.mapper.reflect.internal; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.jdbi.v3.core.config.JdbiConfig; +import org.jdbi.v3.core.internal.JdbiOptionals; + +public class PojoPropertiesFactories implements JdbiConfig { + private final List>>> factories = new ArrayList<>(); + + public PojoPropertiesFactories() {} + + private PojoPropertiesFactories(PojoPropertiesFactories other) { + factories.addAll(other.factories); + } + + public PojoPropertiesFactories register(Function>> factory) { + factories.add(factory); + return this; + } + + public PojoProperties propertiesOf(Type type) { + return factories.stream() + .flatMap(ppf -> JdbiOptionals.stream(ppf.apply(type))) + .findFirst() + .orElseGet(() -> BeanPropertiesFactory.propertiesFor(type)); + } + + @Override + public PojoPropertiesFactories createCopy() { + return new PojoPropertiesFactories(this); + } +} diff --git a/core/src/main/java/org/jdbi/v3/core/qualifier/Qualifiers.java b/core/src/main/java/org/jdbi/v3/core/qualifier/Qualifiers.java index 6de5f9b521..7b57f9b8e3 100644 --- a/core/src/main/java/org/jdbi/v3/core/qualifier/Qualifiers.java +++ b/core/src/main/java/org/jdbi/v3/core/qualifier/Qualifiers.java @@ -13,6 +13,8 @@ */ package org.jdbi.v3.core.qualifier; +import static java.util.stream.Collectors.toSet; + import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.Arrays; @@ -21,8 +23,6 @@ import org.jdbi.v3.meta.Beta; -import static java.util.stream.Collectors.toSet; - /** * Utility class for type qualifiers supported by Jdbi core. */ diff --git a/core/src/main/java/org/jdbi/v3/core/result/ResultBearing.java b/core/src/main/java/org/jdbi/v3/core/result/ResultBearing.java index 69ff493a86..29ba654e72 100644 --- a/core/src/main/java/org/jdbi/v3/core/result/ResultBearing.java +++ b/core/src/main/java/org/jdbi/v3/core/result/ResultBearing.java @@ -179,6 +179,7 @@ default ResultIterable mapTo(QualifiedType type) { * @param the bean type to map the result set rows to * @return a {@link ResultIterable} of the given type. */ + @SuppressWarnings("deprecation") default ResultIterable mapToBean(Class type) { return map(BeanMapper.of(type)); } diff --git a/core/src/main/java/org/jdbi/v3/core/statement/SqlStatement.java b/core/src/main/java/org/jdbi/v3/core/statement/SqlStatement.java index c5cecb2def..48d1863bdb 100644 --- a/core/src/main/java/org/jdbi/v3/core/statement/SqlStatement.java +++ b/core/src/main/java/org/jdbi/v3/core/statement/SqlStatement.java @@ -166,6 +166,7 @@ public This bind(String name, Argument argument) { * * @return modified statement */ + @SuppressWarnings("deprecation") public This bindBean(Object bean) { return bindNamedArgumentFinder(new BeanPropertyArguments(null, bean)); } @@ -180,6 +181,7 @@ public This bindBean(Object bean) { * * @return modified statement */ + @SuppressWarnings("deprecation") public This bindBean(String prefix, Object bean) { return bindNamedArgumentFinder(new BeanPropertyArguments(prefix, bean)); } @@ -192,8 +194,8 @@ public This bindBean(String prefix, Object bean) { * @return modified statement */ @Beta - public This bindProperties(Object pojo) { - return bindNamedArgumentFinder(new PojoPropertyArguments(null, pojo, getContext())); + public This bindPojo(Object pojo) { + return bindPojo(null, pojo); } /** @@ -207,7 +209,7 @@ public This bindProperties(Object pojo) { * @return modified statement */ @Beta - public This bindProperties(String prefix, Object pojo) { + public This bindPojo(String prefix, Object pojo) { return bindNamedArgumentFinder(new PojoPropertyArguments(prefix, pojo, getContext())); } diff --git a/core/src/test/java/org/jdbi/v3/core/mapper/ImmutablesTest.java b/core/src/test/java/org/jdbi/v3/core/mapper/ImmutablesTest.java index ac48ed78e8..73a923d488 100644 --- a/core/src/test/java/org/jdbi/v3/core/mapper/ImmutablesTest.java +++ b/core/src/test/java/org/jdbi/v3/core/mapper/ImmutablesTest.java @@ -19,8 +19,9 @@ import org.immutables.value.Value; import org.jdbi.v3.core.Handle; +import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.generic.GenericType; -import org.jdbi.v3.core.mapper.reflect.ImmutablesMapperFactory; +import org.jdbi.v3.core.mapper.immutables.ImmutablesPlugin; import org.jdbi.v3.core.rule.H2DatabaseRule; import org.junit.Before; import org.junit.Rule; @@ -30,19 +31,24 @@ public class ImmutablesTest { @Rule - public H2DatabaseRule dbRule = new H2DatabaseRule(); + public H2DatabaseRule dbRule = new H2DatabaseRule() + .withPlugin(ImmutablesPlugin.forImmutable(SubValue.class)) + .withPlugin(ImmutablesPlugin.forImmutable(FooBarBaz.class)) + .withPlugin(ImmutablesPlugin.forModifiable(FooBarBaz.class)); + private Jdbi jdbi; private Handle h; @Before public void setup() { + jdbi = dbRule.getJdbi(); h = dbRule.getSharedHandle(); h.execute("create table immutables (t int, x varchar)"); - - h.registerRowMapper(ImmutablesMapperFactory.mapImmutable(SubValue.class, ImmutableSubValue.class, ImmutableSubValue::builder)); } // tag::example[] + // First, install the plugin: ; + @Value.Immutable public interface Train { String name(); @@ -52,12 +58,12 @@ public interface Train { @Test public void simpleTest() { + jdbi.installPlugin(ImmutablesPlugin.forImmutable(Train.class)); h.execute("create table train (name varchar, carriages int, observation_car boolean)"); - h.registerRowMapper(ImmutablesMapperFactory.mapImmutable(Train.class, ImmutableTrain.class, ImmutableTrain::builder)); assertThat( h.createUpdate("insert into train(name, carriages, observation_car) values (:name, :carriages, :observationCar)") - .bindProperties(ImmutableTrain.builder().name("Zephyr").carriages(8).observationCar(true).build()) + .bindPojo(ImmutableTrain.builder().name("Zephyr").carriages(8).observationCar(true).build()) .execute()) .isEqualTo(1); @@ -74,7 +80,7 @@ public void simpleTest() { public void parameterizedTest() { assertThat( h.createUpdate("insert into immutables(t, x) values (:t, :x)") - .bindProperties(ImmutableSubValue.builder().t(42).x("foo").build()) + .bindPojo(ImmutableSubValue.builder().t(42).x("foo").build()) .execute()) .isEqualTo(1); @@ -106,12 +112,10 @@ public interface FooBarBaz { @Test public void testModifiable() { - h.registerRowMapper(ImmutablesMapperFactory.mapImmutable(FooBarBaz.class, ImmutableFooBarBaz.class, ImmutableFooBarBaz::builder)); - h.registerRowMapper(ImmutablesMapperFactory.mapModifiable(FooBarBaz.class, ModifiableFooBarBaz.class, ModifiableFooBarBaz::create)); h.execute("create table fbb (id serial, foo varchar, bar int, baz real)"); assertThat(h.createUpdate("insert into fbb (id, foo, bar, baz) values (:id, :foo, :bar, :baz)") - .bindProperties(ModifiableFooBarBaz.create().setFoo("foo").setBar(42).setBaz(1.0)) + .bindPojo(ModifiableFooBarBaz.create().setFoo("foo").setBar(42).setBaz(1.0)) .execute()) .isEqualTo(1); diff --git a/docs/src/adoc/index.adoc b/docs/src/adoc/index.adoc index a5fce44cc7..74d177c0f3 100644 --- a/docs/src/adoc/index.adoc +++ b/docs/src/adoc/index.adoc @@ -3800,7 +3800,11 @@ to `Jdbi` properties binding and row mapping. [WARNING] Immutables support is still experimental and does not yet support custom naming schemes. -Just tell us about your types, and we do the rest: +Just tell us about your types by installing the plugin (possibly multiple times): + +`jdbi.installPlugin(ImmutablesPlugin.forImmutable(MyValueType.class))` + +and we do the rest: [source,java,indent=0] ---- diff --git a/pom.xml b/pom.xml index 153385ed69..f9db44e7d1 100644 --- a/pom.xml +++ b/pom.xml @@ -84,6 +84,7 @@ 3.4 8.10 0.9.17 + 2.7.1 2.9.8 13.0 1.2.31 @@ -242,7 +243,7 @@ org.immutables value - 2.7.1 + ${dep.immutables.version} @@ -506,6 +507,13 @@ ${project.build.targetJdk} ${project.build.targetJdk} + + + org.immutables + value + ${dep.immutables.version} + + diff --git a/postgres/pom.xml b/postgres/pom.xml index 63d40d17cb..78f9f31bbc 100644 --- a/postgres/pom.xml +++ b/postgres/pom.xml @@ -60,6 +60,7 @@ value test + org.jdbi jdbi3-core diff --git a/postgres/src/test/java/org/jdbi/v3/postgres/TestImmutablesHStore.java b/postgres/src/test/java/org/jdbi/v3/postgres/TestImmutablesHStore.java index a11a443205..cbace59abd 100644 --- a/postgres/src/test/java/org/jdbi/v3/postgres/TestImmutablesHStore.java +++ b/postgres/src/test/java/org/jdbi/v3/postgres/TestImmutablesHStore.java @@ -18,10 +18,10 @@ import org.immutables.value.Value; import org.jdbi.v3.core.Handle; -import org.jdbi.v3.core.mapper.reflect.ImmutablesMapperFactory; +import org.jdbi.v3.core.mapper.immutables.ImmutablesPlugin; import org.jdbi.v3.core.rule.PgDatabaseRule; import org.jdbi.v3.sqlobject.SqlObjectPlugin; -import org.jdbi.v3.sqlobject.customizer.BindProperties; +import org.jdbi.v3.sqlobject.customizer.BindPojo; import org.jdbi.v3.sqlobject.statement.SqlQuery; import org.jdbi.v3.sqlobject.statement.SqlUpdate; import org.junit.After; @@ -34,14 +34,16 @@ public class TestImmutablesHStore { @Rule - public PgDatabaseRule dbRule = new PgDatabaseRule().withPlugin(new PostgresPlugin()).withPlugin(new SqlObjectPlugin()); + public PgDatabaseRule dbRule = new PgDatabaseRule() + .withPlugin(new PostgresPlugin()) + .withPlugin(new SqlObjectPlugin()) + .withPlugin(ImmutablesPlugin.forImmutable(Mappy.class)); private Handle h; @Before public void setup() { h = dbRule.openHandle(); - h.registerRowMapper(ImmutablesMapperFactory.mapImmutable(Mappy.class, ImmutableMappy.class, Mappy::builder)); h.execute("create extension if not exists \"hstore\""); h.execute("create table mappy (numbers hstore not null)"); } @@ -87,7 +89,7 @@ public void testMap() { public interface MappyDao { @SqlUpdate("insert into mappy (numbers) values (:numbers)") - int insert(@BindProperties Mappy mappy); + int insert(@BindPojo Mappy mappy); @SqlQuery("select numbers from mappy") List select(); diff --git a/sqlobject/src/main/java/org/jdbi/v3/sqlobject/config/internal/RegisterBeanMapperImpl.java b/sqlobject/src/main/java/org/jdbi/v3/sqlobject/config/internal/RegisterBeanMapperImpl.java index 78507bc75b..fe662f42b3 100644 --- a/sqlobject/src/main/java/org/jdbi/v3/sqlobject/config/internal/RegisterBeanMapperImpl.java +++ b/sqlobject/src/main/java/org/jdbi/v3/sqlobject/config/internal/RegisterBeanMapperImpl.java @@ -24,6 +24,7 @@ public class RegisterBeanMapperImpl implements Configurer { @Override + @SuppressWarnings("deprecation") public void configureForType(ConfigRegistry registry, Annotation annotation, Class sqlObjectType) { RegisterBeanMapper registerBeanMapper = (RegisterBeanMapper) annotation; Class beanClass = registerBeanMapper.value(); diff --git a/sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/BindProperties.java b/sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/BindPojo.java similarity index 86% rename from sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/BindProperties.java rename to sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/BindPojo.java index 02b222cf81..e3913d99b5 100644 --- a/sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/BindProperties.java +++ b/sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/BindPojo.java @@ -18,15 +18,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.jdbi.v3.sqlobject.customizer.internal.BindPropertiesFactory; +import org.jdbi.v3.sqlobject.customizer.internal.BindPojoFactory; /** * Binds the properties of an object to a SQL statement. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) -@SqlStatementCustomizingAnnotation(BindPropertiesFactory.class) -public @interface BindProperties { +@SqlStatementCustomizingAnnotation(BindPojoFactory.class) +public @interface BindPojo { /** * Prefix to apply to each property. If specified, properties will be bound as * {@code prefix.propertyName}. diff --git a/sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/internal/BindPropertiesFactory.java b/sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/internal/BindPojoFactory.java similarity index 84% rename from sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/internal/BindPropertiesFactory.java rename to sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/internal/BindPojoFactory.java index 3b85f73708..af837ce305 100644 --- a/sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/internal/BindPropertiesFactory.java +++ b/sqlobject/src/main/java/org/jdbi/v3/sqlobject/customizer/internal/BindPojoFactory.java @@ -18,11 +18,11 @@ import java.lang.reflect.Parameter; import java.lang.reflect.Type; -import org.jdbi.v3.sqlobject.customizer.BindProperties; +import org.jdbi.v3.sqlobject.customizer.BindPojo; import org.jdbi.v3.sqlobject.customizer.SqlStatementCustomizerFactory; import org.jdbi.v3.sqlobject.customizer.SqlStatementParameterCustomizer; -public class BindPropertiesFactory implements SqlStatementCustomizerFactory { +public class BindPojoFactory implements SqlStatementCustomizerFactory { @Override public SqlStatementParameterCustomizer createForParameter(Annotation annotation, Class sqlObjectType, @@ -30,13 +30,13 @@ public SqlStatementParameterCustomizer createForParameter(Annotation annotation, Parameter param, int index, Type type) { - BindProperties bind = (BindProperties) annotation; + BindPojo bind = (BindPojo) annotation; return (stmt, bean) -> { String prefix = bind.value(); if (prefix.isEmpty()) { - stmt.bindProperties(bean); + stmt.bindPojo(bean); } else { - stmt.bindProperties(prefix, bean); + stmt.bindPojo(prefix, bean); } }; } diff --git a/sqlobject/src/test/java/org/jdbi/v3/sqlobject/TestBindProperties.java b/sqlobject/src/test/java/org/jdbi/v3/sqlobject/TestBindProperties.java index 3e92b558a9..7c830edd0a 100644 --- a/sqlobject/src/test/java/org/jdbi/v3/sqlobject/TestBindProperties.java +++ b/sqlobject/src/test/java/org/jdbi/v3/sqlobject/TestBindProperties.java @@ -18,9 +18,9 @@ import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.mapper.ImmutableTrain; import org.jdbi.v3.core.mapper.ImmutablesTest.Train; -import org.jdbi.v3.core.mapper.reflect.ImmutablesMapperFactory; +import org.jdbi.v3.core.mapper.immutables.ImmutablesPlugin; import org.jdbi.v3.core.rule.H2DatabaseRule; -import org.jdbi.v3.sqlobject.customizer.BindProperties; +import org.jdbi.v3.sqlobject.customizer.BindPojo; import org.jdbi.v3.sqlobject.statement.SqlQuery; import org.jdbi.v3.sqlobject.statement.SqlUpdate; import org.junit.Before; @@ -31,7 +31,9 @@ public class TestBindProperties { @Rule - public H2DatabaseRule dbRule = new H2DatabaseRule().withPlugin(new SqlObjectPlugin()); + public H2DatabaseRule dbRule = new H2DatabaseRule() + .withPlugin(new SqlObjectPlugin()) + .withPlugin(ImmutablesPlugin.forImmutable(Train.class)); private Handle h; @@ -40,7 +42,6 @@ public class TestBindProperties { @Before public void setUp() { h = dbRule.getSharedHandle(); - h.registerRowMapper(ImmutablesMapperFactory.mapImmutable(Train.class, ImmutableTrain.class, ImmutableTrain::builder)); h.execute("create table train (name varchar, carriages int, observation_car boolean)"); dao = h.attach(Dao.class); @@ -63,7 +64,7 @@ public void testBindBean() { public interface Dao { @SqlUpdate("insert into train(name, carriages, observation_car) values (:name, :carriages, :observationCar)") - int insert(@BindProperties Train train); + int insert(@BindPojo Train train); @SqlQuery("select * from train") List getTrains();