diff --git a/jOOQ-examples/jOOQ-jpa-example/pom.xml b/jOOQ-examples/jOOQ-jpa-example/pom.xml index 8b93b8c932..bb2379a4cc 100644 --- a/jOOQ-examples/jOOQ-jpa-example/pom.xml +++ b/jOOQ-examples/jOOQ-jpa-example/pom.xml @@ -138,13 +138,6 @@ .* - - - java.time.Year - org.jooq.Converter.of(Integer.class, Year.class, new org.jooq.example.jpa.converters.YearConverter()::convertToEntityAttribute, new org.jooq.example.jpa.converters.YearConverter()::convertToDatabaseColumn) - RELEASE_YEAR - - diff --git a/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/Public.java b/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/Public.java index ab283f442f..6b8181e2cd 100644 --- a/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/Public.java +++ b/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/Public.java @@ -33,7 +33,7 @@ import org.jooq.impl.SchemaImpl; @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Public extends SchemaImpl { - private static final long serialVersionUID = 1252977499; + private static final long serialVersionUID = 897816362; /** * The reference instance of PUBLIC @@ -85,9 +85,9 @@ public class Public extends SchemaImpl { private final List> getSequences0() { return Arrays.>asList( - Sequences.SYSTEM_SEQUENCE_5B4EAFE5_365F_441D_BC0D_72B0BA27E6CC, - Sequences.SYSTEM_SEQUENCE_65506620_5C73_4217_9913_7AB008A3F158, - Sequences.SYSTEM_SEQUENCE_805FDF72_C4BB_4AA3_86DB_0EA4233F1A7C); + Sequences.SYSTEM_SEQUENCE_89CD2855_22EA_4F63_95A8_18A79F6EC782, + Sequences.SYSTEM_SEQUENCE_BE504C6E_CD12_493D_BEC3_FE5DEDA7FE1A, + Sequences.SYSTEM_SEQUENCE_C6201643_1CF6_4072_8443_E47AC449BE9D); } @Override diff --git a/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/Sequences.java b/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/Sequences.java index 1cd091ec9b..7b36b13bec 100644 --- a/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/Sequences.java +++ b/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/Sequences.java @@ -24,17 +24,17 @@ import org.jooq.impl.SequenceImpl; public class Sequences { /** - * The sequence PUBLIC.SYSTEM_SEQUENCE_5B4EAFE5_365F_441D_BC0D_72B0BA27E6CC + * The sequence PUBLIC.SYSTEM_SEQUENCE_89CD2855_22EA_4F63_95A8_18A79F6EC782 */ - public static final Sequence SYSTEM_SEQUENCE_5B4EAFE5_365F_441D_BC0D_72B0BA27E6CC = new SequenceImpl("SYSTEM_SEQUENCE_5B4EAFE5_365F_441D_BC0D_72B0BA27E6CC", Public.PUBLIC, org.jooq.impl.SQLDataType.BIGINT); + public static final Sequence SYSTEM_SEQUENCE_89CD2855_22EA_4F63_95A8_18A79F6EC782 = new SequenceImpl("SYSTEM_SEQUENCE_89CD2855_22EA_4F63_95A8_18A79F6EC782", Public.PUBLIC, org.jooq.impl.SQLDataType.BIGINT); /** - * The sequence PUBLIC.SYSTEM_SEQUENCE_65506620_5C73_4217_9913_7AB008A3F158 + * The sequence PUBLIC.SYSTEM_SEQUENCE_BE504C6E_CD12_493D_BEC3_FE5DEDA7FE1A */ - public static final Sequence SYSTEM_SEQUENCE_65506620_5C73_4217_9913_7AB008A3F158 = new SequenceImpl("SYSTEM_SEQUENCE_65506620_5C73_4217_9913_7AB008A3F158", Public.PUBLIC, org.jooq.impl.SQLDataType.BIGINT); + public static final Sequence SYSTEM_SEQUENCE_BE504C6E_CD12_493D_BEC3_FE5DEDA7FE1A = new SequenceImpl("SYSTEM_SEQUENCE_BE504C6E_CD12_493D_BEC3_FE5DEDA7FE1A", Public.PUBLIC, org.jooq.impl.SQLDataType.BIGINT); /** - * The sequence PUBLIC.SYSTEM_SEQUENCE_805FDF72_C4BB_4AA3_86DB_0EA4233F1A7C + * The sequence PUBLIC.SYSTEM_SEQUENCE_C6201643_1CF6_4072_8443_E47AC449BE9D */ - public static final Sequence SYSTEM_SEQUENCE_805FDF72_C4BB_4AA3_86DB_0EA4233F1A7C = new SequenceImpl("SYSTEM_SEQUENCE_805FDF72_C4BB_4AA3_86DB_0EA4233F1A7C", Public.PUBLIC, org.jooq.impl.SQLDataType.BIGINT); + public static final Sequence SYSTEM_SEQUENCE_C6201643_1CF6_4072_8443_E47AC449BE9D = new SequenceImpl("SYSTEM_SEQUENCE_C6201643_1CF6_4072_8443_E47AC449BE9D", Public.PUBLIC, org.jooq.impl.SQLDataType.BIGINT); } diff --git a/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/tables/Film.java b/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/tables/Film.java index c4e59ec296..e7df2c63cb 100644 --- a/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/tables/Film.java +++ b/jOOQ-examples/jOOQ-jpa-example/src/main/java/org/jooq/example/jpa/jooq/tables/Film.java @@ -40,7 +40,7 @@ import org.jooq.impl.TableImpl; @SuppressWarnings({ "all", "unchecked", "rawtypes" }) public class Film extends TableImpl { - private static final long serialVersionUID = 1113845742; + private static final long serialVersionUID = 1030256935; /** * The reference instance of PUBLIC.FILM @@ -68,7 +68,7 @@ public class Film extends TableImpl { /** * The column PUBLIC.FILM.RELEASE_YEAR. */ - public final TableField RELEASE_YEAR = createField("RELEASE_YEAR", org.jooq.impl.SQLDataType.INTEGER, this, "", org.jooq.Converter.of(Integer.class, Year.class, new org.jooq.example.jpa.converters.YearConverter()::convertToEntityAttribute, new org.jooq.example.jpa.converters.YearConverter()::convertToDatabaseColumn)); + public final TableField RELEASE_YEAR = createField("RELEASE_YEAR", org.jooq.impl.SQLDataType.INTEGER, this, "", new org.jooq.impl.JPAConverter(org.jooq.example.jpa.converters.YearConverter.class)); /** * The column PUBLIC.FILM.TITLE. diff --git a/jOOQ-manual/src/main/resources/org/jooq/web/manual-3.10.xml b/jOOQ-manual/src/main/resources/org/jooq/web/manual-3.10.xml index eb9b034b41..e5e8deee13 100644 --- a/jOOQ-manual/src/main/resources/org/jooq/web/manual-3.10.xml +++ b/jOOQ-manual/src/main/resources/org/jooq/web/manual-3.10.xml @@ -17673,6 +17673,14 @@ public class Book { packages com.example.entities + + + + use-attribute-converters + true + ]]> diff --git a/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/JPADatabase.java b/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/JPADatabase.java index 25ae3ff4ce..a514b15449 100644 --- a/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/JPADatabase.java +++ b/jOOQ-meta-extensions/src/main/java/org/jooq/util/jpa/JPADatabase.java @@ -37,22 +37,30 @@ package org.jooq.util.jpa; import static org.jooq.tools.StringUtils.defaultIfBlank; import static org.jooq.tools.StringUtils.isBlank; +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collection; import java.util.EnumSet; import java.util.Iterator; import java.util.List; +import java.util.Map.Entry; +import javax.persistence.AttributeConverter; import javax.persistence.Entity; import org.jooq.DSLContext; +import org.jooq.Name; +import org.jooq.SQLDialect; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; +import org.jooq.impl.JPAConverter; import org.jooq.tools.JooqLogger; import org.jooq.util.SchemaDefinition; import org.jooq.util.h2.H2Database; +import org.jooq.util.jaxb.ForcedType; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; @@ -84,11 +92,11 @@ import org.springframework.core.type.filter.AnnotationTypeFilter; */ public class JPADatabase extends H2Database { - private static final JooqLogger log = JooqLogger.getLogger(JPADatabase.class); + static final String HIBERNATE_DIALECT = SQLDialect.H2.thirdParty().hibernateDialect(); + static final JooqLogger log = JooqLogger.getLogger(JPADatabase.class); - private Connection connection; + private Connection connection; - @SuppressWarnings("serial") @Override protected DSLContext create0() { if (connection == null) { @@ -99,38 +107,19 @@ public class JPADatabase extends H2Database { log.warn("No packages defined", "It is highly recommended that you provide explicit packages to scan"); } + boolean useAttributeConverters = Boolean.valueOf(getProperties().getProperty("use-attribute-converters", "true")); + try { connection = DriverManager.getConnection("jdbc:h2:mem:jooq-meta-extensions", "sa", ""); MetadataSources metadata = new MetadataSources( new StandardServiceRegistryBuilder() - .applySetting("hibernate.dialect", "org.hibernate.dialect.H2Dialect") + .applySetting("hibernate.dialect", HIBERNATE_DIALECT) .applySetting("javax.persistence.schema-generation-connection", connection) .applySetting("javax.persistence.create-database-schemas", true) // [#5607] JPADatabase causes warnings - This prevents them - .applySetting(AvailableSettings.CONNECTION_PROVIDER, new ConnectionProvider() { - @SuppressWarnings("rawtypes") - @Override - public boolean isUnwrappableAs(Class unwrapType) { - return false; - } - @Override - public T unwrap(Class unwrapType) { - return null; - } - @Override - public Connection getConnection() { - return connection; - } - @Override - public void closeConnection(Connection conn) throws SQLException {} - - @Override - public boolean supportsAggressiveRelease() { - return true; - } - }) + .applySetting(AvailableSettings.CONNECTION_PROVIDER, connectionProvider()) .build() ); @@ -154,6 +143,9 @@ public class JPADatabase extends H2Database { // Hibernate 5.2 broke 5.0 API again. Here's how to do this now: SchemaExport export = new SchemaExport(); export.create(EnumSet.of(TargetType.DATABASE), metadata.buildMetadata()); + + if (useAttributeConverters) + loadAttributeConverters(metadata.getAnnotatedClasses()); } catch (Exception e) { throw new DataAccessException("Error while exporting schema", e); @@ -163,6 +155,71 @@ public class JPADatabase extends H2Database { return DSL.using(connection); } + @SuppressWarnings("serial") + ConnectionProvider connectionProvider() { + return new ConnectionProvider() { + @SuppressWarnings("rawtypes") + @Override + public boolean isUnwrappableAs(Class unwrapType) { + return false; + } + @Override + public T unwrap(Class unwrapType) { + return null; + } + @Override + public Connection getConnection() { + return connection; + } + @Override + public void closeConnection(Connection conn) {} + + @Override + public boolean supportsAggressiveRelease() { + return true; + } + }; + } + + private final void loadAttributeConverters(Collection> classes) { + try { + AttributeConverterExtractor extractor = new AttributeConverterExtractor(this, classes); + + attributesLoop: + for (Entry> entry : extractor.extract().entrySet()) { + Class convertToEntityAttribute = null; + + for (Method method : entry.getValue().getClass().getMethods()) + if ("convertToEntityAttribute".equals(method.getName())) + convertToEntityAttribute = method.getReturnType(); + + if (convertToEntityAttribute == null) { + log.info("AttributeConverter", "Cannot use AttributeConverter: " + entry.getValue().getClass().getName()); + continue attributesLoop; + } + + // Tables can be fully or partially or not at all qualified. Let's just accept any prefix + // to the available qualification + String regex = "(.*?\\.)?" + entry.getKey().unquotedName().toString().replace(".", "\\."); + ForcedType forcedType = new ForcedType() + .withExpression("(?i:" + regex + ")") + .withUserType(convertToEntityAttribute.getName()) + .withConverter(String.format("new %s(%s.class)", + JPAConverter.class.getName(), + entry.getValue().getClass().getName() + )); + + log.info("AttributeConverter", "Configuring JPA AttributeConverter: " + toString(forcedType)); + getConfiguredForcedTypes().add(forcedType); + } + } + + // AttributeConverter is part of JPA 2.1. Older JPA providers may not have this type, yet + catch (NoClassDefFoundError e) { + log.info("AttributeConverter", "Cannot load AttributeConverters: " + e.getMessage()); + } + } + @Override protected List getSchemata0() throws SQLException { List result = new ArrayList(super.getSchemata0()); diff --git a/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java b/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java index d4251d2f69..0877492abe 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java @@ -997,7 +997,7 @@ public abstract class AbstractDatabase implements Database { } @SuppressWarnings({ "unchecked", "rawtypes" }) - static final String toString(final Object object) { + protected static final String toString(final Object object) { final StringWriter writer = new StringWriter(); try {