diff --git a/hibernate-types-60/pom.xml b/hibernate-types-60/pom.xml
new file mode 100644
index 000000000..eee14d01c
--- /dev/null
+++ b/hibernate-types-60/pom.xml
@@ -0,0 +1,110 @@
+
+
+
+ com.vladmihalcea
+ hibernate-types-parent
+ 2.14.2-SNAPSHOT
+
+
+ 4.0.0
+
+ hibernate-types-60
+ 2.14.2-SNAPSHOT
+ jar
+
+ hibernate-types-60
+ Hibernate ORM 6.0 extra Types
+
+
+
+
+ org.hibernate
+ hibernate-core
+ ${hibernate.version}
+ provided
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+ provided
+ true
+
+
+
+ com.fasterxml.jackson.module
+ jackson-module-jaxb-annotations
+ ${jackson.version}
+
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+ provided
+ true
+
+
+
+ org.hibernate
+ hibernate-jcache
+ ${hibernate.version}
+ test
+
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+ provided
+ true
+
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.0
+ provided
+ true
+
+
+
+
+
+ 11
+
+ 6.0.0.Final
+ 42.3.3
+
+ 8.0.28
+ 2.11.0
+ 29.0-jre
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven-jar-plugin.version}
+
+
+
+ com.vladmihalcea.hibernate.type
+
+
+
+
+
+
+ test-jar
+
+
+
+
+
+
+
+
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/id/BatchSequenceGenerator.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/id/BatchSequenceGenerator.java
new file mode 100644
index 000000000..cf1f4ef68
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/id/BatchSequenceGenerator.java
@@ -0,0 +1,404 @@
+package com.vladmihalcea.hibernate.id;
+
+import org.hibernate.HibernateException;
+import org.hibernate.MappingException;
+import org.hibernate.boot.model.relational.Database;
+import org.hibernate.boot.model.relational.QualifiedNameParser;
+import org.hibernate.boot.model.relational.SqlStringGenerationContext;
+import org.hibernate.dialect.Dialect;
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
+import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.id.BulkInsertionCapableIdentifierGenerator;
+import org.hibernate.id.Configurable;
+import org.hibernate.id.IdentifierGenerationException;
+import org.hibernate.id.PersistentIdentifierGenerator;
+import org.hibernate.id.enhanced.DatabaseStructure;
+import org.hibernate.id.enhanced.Optimizer;
+import org.hibernate.id.enhanced.SequenceStructure;
+import org.hibernate.internal.util.config.ConfigurationHelper;
+import org.hibernate.service.ServiceRegistry;
+import org.hibernate.type.Type;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A sequence generator that uses a recursive query to fetch multiple
+ * values from a sequence in a single database access.
+ *
+ *
Parameters
+ * The following configuration parameters are supported:
+ *
+ * {@value #SEQUENCE_PARAM}
+ * mandatory , name of the sequence to use
+ * {@value #FETCH_SIZE_PARAM}
+ * optional, how many sequence numbers should be fetched at a time,
+ * default is {@value #DEFAULT_FETCH_SIZE}
+ *
+ *
+ * SQL
+ * Per default the generated SELECT will look something like this
+ *
+ * WITH RECURSIVE t(n, level_num) AS (
+ * SELECT nextval(seq_xxx) AS n, 1 AS level_num
+ * UNION ALL
+ * SELECT nextval(seq_xxx) AS n, level_num + 1 AS level_num
+ * FROM t
+ * WHERE level_num < ?)
+ * SELECT n
+ * FROM t;
+ *
+ *
+ * DB2
+ * For DB2 the generated SELECT will look something like this
+ *
+ * WITH t(n) AS (
+ * SELECT 1 AS n
+ * FROM (VALUES 1)
+ * UNION ALL
+ * SELECT n + 1 AS n
+ * FROM t
+ * WHERE n < ?)
+ * SELECT next value for SEQ_CHILD_ID AS n
+ * FROM t;
+ *
+ *
+ * HSQLDB
+ * For HSQLDB the generated SELECT will look something like this
+ *
+ * SELECT next value for seq_parent_id
+ * FROM UNNEST(SEQUENCE_ARRAY(1, ?, 1));
+ *
+ *
+ * Oracle
+ * For Oracle the generated SELECT will look something like this
+ * because Oracle does not support using recursive common table
+ * expressions to fetch multiple values from a sequence.
+ *
+ * SELECT seq_xxx.nextval
+ * FROM dual
+ * CONNECT BY rownum <= ?
+ *
+ *
+ * SQL Server
+ * For SQL Server the generated SELECT will look something like this
+ *
+ * WITH t(n) AS (
+ * SELECT 1 AS n
+ * UNION ALL
+ * SELECT n + 1 AS n
+ * FROM t
+ * WHERE n < ?)
+ * SELECT NEXT VALUE FOR seq_xxx AS n
+ * FROM t
+ *
+ *
+ * Firebird
+ * For Firebird the generated SELECT will look something like this
+ *
+ * WITH RECURSIVE t(n, level_num) AS (
+ * SELECT NEXT VALUE FOR seq_xxx AS n, 1 AS level_num
+ * FROM rdb$database
+ * UNION ALL
+ * SELECT NEXT VALUE FOR seq_xxx AS n, level_num + 1 AS level_num
+ * FROM t
+ * WHERE level_num < ?)
+ * SELECT n
+ * FROM t
+ *
+ *
+ * Database Support
+ * The following RDBMS have been verified to work
+ *
+ * DB2
+ * Firebird
+ * Oracle
+ * H2
+ * HSQLDB
+ * MariaDB
+ * Postgres
+ * SQL Sever
+ *
+ *
+ * In theory any RDBMS that supports {@code WITH RECURSIVE} and
+ * sequences is supported.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Philippe Marschall
+ * @since 2.14.0
+ */
+public class BatchSequenceGenerator implements BulkInsertionCapableIdentifierGenerator, PersistentIdentifierGenerator, Configurable {
+
+ /**
+ * Indicates the name of the sequence to use, mandatory.
+ */
+ public static final String SEQUENCE_PARAM = "sequence";
+
+ /**
+ * Indicates how many sequence values to fetch at once. The default value is {@link #DEFAULT_FETCH_SIZE}.
+ */
+ public static final String FETCH_SIZE_PARAM = "fetch_size";
+
+ /**
+ * The default value for {@link #FETCH_SIZE_PARAM}.
+ */
+ public static final int DEFAULT_FETCH_SIZE = 10;
+
+ private final Lock lock = new ReentrantLock();
+
+ private String select;
+ private int fetchSize;
+ private IdentifierPool identifierPool;
+ private IdentifierExtractor identifierExtractor;
+ private DatabaseStructure databaseStructure;
+
+ @Override
+ public void configure(Type type, Properties params, ServiceRegistry serviceRegistry)
+ throws MappingException {
+
+ JdbcEnvironment jdbcEnvironment = serviceRegistry.getService(JdbcEnvironment.class);
+ Dialect dialect = jdbcEnvironment.getDialect();
+ String sequenceName = determineSequenceName(params);
+ this.fetchSize = determineFetchSize(params);
+
+ this.select = buildSelect(sequenceName, dialect);
+ this.identifierExtractor = IdentifierExtractor.getIdentifierExtractor(type.getReturnedClass());
+ this.identifierPool = IdentifierPool.empty();
+
+ this.databaseStructure = this.buildDatabaseStructure(type, sequenceName, jdbcEnvironment);
+ }
+
+ private static String buildSelect(String sequenceName, Dialect dialect) {
+ if (dialect instanceof org.hibernate.dialect.Oracle8iDialect) {
+ return "SELECT " + dialect.getSequenceSupport().getSelectSequenceNextValString(sequenceName) + " FROM dual CONNECT BY rownum <= ?";
+ }
+ if (dialect instanceof org.hibernate.dialect.SQLServerDialect) {
+ // No RECURSIVE
+ return "WITH t(n) AS ( "
+ + "SELECT 1 AS n "
+ + "UNION ALL "
+ +"SELECT n + 1 AS n FROM t WHERE n < ?) "
+ // sequence generation outside of WITH
+ + "SELECT " + dialect.getSequenceSupport().getSelectSequenceNextValString(sequenceName) + " AS n FROM t";
+ }
+ if (dialect instanceof org.hibernate.dialect.DB2Dialect) {
+ // No RECURSIVE
+ return "WITH t(n) AS ( "
+ + "SELECT 1 AS n "
+ // difference
+ + "FROM (VALUES 1) "
+ + "UNION ALL "
+ +"SELECT n + 1 AS n FROM t WHERE n < ?) "
+ // sequence generation outside of WITH
+ + "SELECT " + dialect.getSequenceSupport().getSelectSequenceNextValString(sequenceName) + " AS n FROM t";
+ }
+ if (dialect instanceof org.hibernate.dialect.HSQLDialect) {
+ // https://stackoverflow.com/questions/44472280/cte-based-sequence-generation-with-hsqldb/52329862
+ return "SELECT " + dialect.getSequenceSupport().getSelectSequenceNextValString(sequenceName) + " FROM UNNEST(SEQUENCE_ARRAY(1, ?, 1))";
+ }
+ return "WITH RECURSIVE t(n, level_num) AS ("
+ + "SELECT " + dialect.getSequenceSupport().getSelectSequenceNextValString(sequenceName) + " AS n, 1 AS level_num "
+ + "UNION ALL "
+ + "SELECT " + dialect.getSequenceSupport().getSelectSequenceNextValString(sequenceName) + " AS n, level_num + 1 AS level_num "
+ + " FROM t "
+ + " WHERE level_num < ?) "
+ + "SELECT n FROM t";
+ }
+
+ private SequenceStructure buildDatabaseStructure(Type type, String sequenceName, JdbcEnvironment jdbcEnvironment) {
+ return new SequenceStructure(jdbcEnvironment, "orm",
+ QualifiedNameParser.INSTANCE.parse(sequenceName), 1, 1, type.getReturnedClass());
+ }
+
+ private static String determineSequenceName(Properties params) {
+ String sequenceName = params.getProperty(SEQUENCE_PARAM);
+ if (sequenceName == null) {
+ throw new MappingException("no squence name specified");
+ }
+ return sequenceName;
+ }
+
+ private static int determineFetchSize(Properties params) {
+ int fetchSize = ConfigurationHelper.getInt(FETCH_SIZE_PARAM, params, DEFAULT_FETCH_SIZE);
+ if (fetchSize <= 0) {
+ throw new MappingException("fetch size must be positive");
+ }
+ return fetchSize;
+ }
+
+ @Override
+ public boolean supportsBulkInsertionIdentifierGeneration() {
+ return true;
+ }
+
+ @Override
+ public String determineBulkInsertionIdentifierGenerationSelectFragment(SqlStringGenerationContext sqlStringGenerationContext) {
+ return sqlStringGenerationContext.getDialect().getSequenceSupport().getSequenceNextValString(this.getSequenceName());
+ }
+
+ @Override
+ public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
+ this.lock.lock();
+ try {
+ if (this.identifierPool.isEmpty()) {
+ this.identifierPool = this.replenishIdentifierPool(session);
+ }
+ return this.identifierPool.next();
+ } finally {
+ this.lock.unlock();
+ }
+ }
+
+ private String getSequenceName() {
+ return this.databaseStructure.getPhysicalName().render();
+ }
+
+ @Override
+ public void registerExportables(Database database) {
+ this.databaseStructure.registerExportables(database);
+ }
+
+ private IdentifierPool replenishIdentifierPool(SharedSessionContractImplementor session)
+ throws HibernateException {
+ JdbcCoordinator coordinator = session.getJdbcCoordinator();
+ List identifiers = new ArrayList<>(this.fetchSize);
+ try (PreparedStatement statement = coordinator.getStatementPreparer().prepareStatement(this.select)) {
+ statement.setFetchSize(this.fetchSize);
+ statement.setInt(1, this.fetchSize);
+ try (ResultSet resultSet = coordinator.getResultSetReturn().extract(statement)) {
+ while (resultSet.next()) {
+ identifiers.add(this.identifierExtractor.extractIdentifier(resultSet));
+ }
+ }
+ } catch (SQLException e) {
+ throw session.getJdbcServices().getSqlExceptionHelper().convert(
+ e, "could not get next sequence value", this.select);
+ }
+ if (identifiers.size() != this.fetchSize) {
+ throw new IdentifierGenerationException("expected " + this.fetchSize + " values from " + this.getSequenceName()
+ + " but got " + identifiers.size());
+ }
+ return IdentifierPool.forList(identifiers);
+ }
+
+ @Override
+ public Optimizer getOptimizer() {
+ return null;
+ }
+
+ /**
+ * Holds a number of prefetched identifiers.
+ */
+ static final class IdentifierPool {
+
+ private final Iterator iterator;
+
+ private IdentifierPool (List identifiers) {
+ this.iterator = identifiers.iterator();
+ }
+
+ static IdentifierPool forList(List identifiers) {
+ return new IdentifierPool(identifiers);
+ }
+
+ static IdentifierPool empty() {
+ return new IdentifierPool(Collections.emptyList());
+ }
+
+ boolean isEmpty() {
+ return !this.iterator.hasNext();
+ }
+
+ Serializable next() {
+ return this.iterator.next();
+ }
+
+ }
+
+ /**
+ * Extracts a {@link Serializable} identifier from a {@link ResultSet}.
+ *
+ * @see org.hibernate.id.IntegralDataTypeHolder
+ */
+ enum IdentifierExtractor {
+
+ INTEGER_IDENTIFIER_EXTRACTOR {
+ @Override
+ Serializable extractIdentifier(ResultSet resultSet) throws SQLException {
+ int intValue = resultSet.getInt(1);
+ if (resultSet.wasNull()) {
+ throw new IdentifierGenerationException("sequence returned null");
+ }
+ return intValue;
+ }
+ },
+
+ LONG_IDENTIFIER_EXTRACTOR {
+ @Override
+ Serializable extractIdentifier(ResultSet resultSet) throws SQLException {
+ long longValue = resultSet.getLong(1);
+ if (resultSet.wasNull()) {
+ throw new IdentifierGenerationException("sequence returned null");
+ }
+ return longValue;
+ }
+ },
+
+ BIG_INTEGER_IDENTIFIER_EXTRACTOR {
+ @Override
+ Serializable extractIdentifier(ResultSet resultSet) throws SQLException {
+ BigDecimal bigDecimal = resultSet.getBigDecimal(1);
+ if (resultSet.wasNull()) {
+ throw new IdentifierGenerationException("sequence returned null");
+ }
+ return bigDecimal.setScale(0, BigDecimal.ROUND_UNNECESSARY).toBigInteger();
+ }
+ },
+
+ BIG_DECIMAL_IDENTIFIER_EXTRACTOR {
+ @Override
+ Serializable extractIdentifier(ResultSet resultSet) throws SQLException {
+ BigDecimal bigDecimal = resultSet.getBigDecimal(1);
+ if (resultSet.wasNull()) {
+ throw new IdentifierGenerationException("sequence returned null");
+ }
+ return bigDecimal;
+ }
+ };
+
+ abstract Serializable extractIdentifier(ResultSet resultSet) throws SQLException;
+
+ static IdentifierExtractor getIdentifierExtractor(Class> integralType) {
+ if ((integralType == Integer.class) || (integralType == int.class)) {
+ return INTEGER_IDENTIFIER_EXTRACTOR;
+ }
+ if ((integralType == Long.class) || (integralType == long.class)) {
+ return LONG_IDENTIFIER_EXTRACTOR;
+ }
+ if (integralType == BigInteger.class) {
+ return BIG_INTEGER_IDENTIFIER_EXTRACTOR;
+ }
+ if (integralType == BigDecimal.class) {
+ return BIG_DECIMAL_IDENTIFIER_EXTRACTOR;
+ }
+ throw new IdentifierGenerationException("unsupported integral type: " + integralType);
+ }
+
+ }
+
+ @Override
+ public String toString() {
+ // for debugging only
+ return this.getClass().getSimpleName() + '(' + this.getSequenceName() + ')';
+ }
+
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/naming/CamelCaseToSnakeCaseNamingStrategy.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/naming/CamelCaseToSnakeCaseNamingStrategy.java
new file mode 100644
index 000000000..64b13c13d
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/naming/CamelCaseToSnakeCaseNamingStrategy.java
@@ -0,0 +1,80 @@
+package com.vladmihalcea.hibernate.naming;
+
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.boot.model.naming.Identifier;
+import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
+import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
+
+/**
+ * Maps the JPA camelCase properties to snake_case database identifiers.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class CamelCaseToSnakeCaseNamingStrategy extends PhysicalNamingStrategyStandardImpl {
+
+ public static final CamelCaseToSnakeCaseNamingStrategy INSTANCE = new CamelCaseToSnakeCaseNamingStrategy();
+
+ public static final String CAMEL_CASE_REGEX = "([a-z]+)([A-Z]+)";
+
+ public static final String SNAKE_CASE_PATTERN = "$1\\_$2";
+
+ private final Configuration configuration;
+
+ /**
+ * Initialization constructor taking the default {@link Configuration} object.
+ */
+ public CamelCaseToSnakeCaseNamingStrategy() {
+ this.configuration = Configuration.INSTANCE;
+ }
+
+ /**
+ * Initialization constructor taking the {@link Class} and {@link Configuration} objects.
+ *
+ * @param configuration custom {@link Configuration} object.
+ */
+ public CamelCaseToSnakeCaseNamingStrategy(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ @Override
+ public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) {
+ return formatIdentifier(super.toPhysicalCatalogName(name, context));
+ }
+
+ @Override
+ public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) {
+ return formatIdentifier(super.toPhysicalSchemaName(name, context));
+ }
+
+ @Override
+ public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
+ return formatIdentifier(super.toPhysicalTableName(name, context));
+ }
+
+ @Override
+ public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {
+ return formatIdentifier(super.toPhysicalSequenceName(name, context));
+ }
+
+ @Override
+ public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
+ return formatIdentifier(super.toPhysicalColumnName(name, context));
+ }
+
+ private Identifier formatIdentifier(Identifier identifier) {
+ if (identifier != null) {
+ String name = identifier.getText();
+
+ String formattedName = name.replaceAll(CAMEL_CASE_REGEX, SNAKE_CASE_PATTERN).toLowerCase();
+
+ return !formattedName.equals(name) ?
+ Identifier.toIdentifier(formattedName, identifier.isQuoted()) :
+ identifier;
+ } else {
+ return null;
+ }
+
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/ListResultTransformer.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/ListResultTransformer.java
new file mode 100644
index 000000000..fa2883ea6
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/ListResultTransformer.java
@@ -0,0 +1,33 @@
+package com.vladmihalcea.hibernate.query;
+
+import org.hibernate.transform.ResultTransformer;
+
+import java.util.List;
+
+/**
+ * The {@link ListResultTransformer} simplifies the way
+ * we can use a ResultTransformer by defining a default implementation for the
+ * {@link ResultTransformer#transformList(List)} method.
+ *
+ * This way, the {@link ListResultTransformer} can be used
+ * as a functional interface.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ * @since 2.9.0
+ */
+@FunctionalInterface
+public interface ListResultTransformer extends ResultTransformer {
+
+ /**
+ * Default implementation returning the tuples list as-is.
+ *
+ * @param tuples tuples list
+ * @return tuples list
+ */
+ @Override
+ default List transformList(List tuples) {
+ return tuples;
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/MapResultTransformer.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/MapResultTransformer.java
new file mode 100644
index 000000000..8e3189021
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/MapResultTransformer.java
@@ -0,0 +1,95 @@
+package com.vladmihalcea.hibernate.query;
+
+import org.hibernate.HibernateException;
+import org.hibernate.transform.ResultTransformer;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The {@link MapResultTransformer} allows us to return
+ * a {@link Map} from a JPA {@link jakarta.persistence.Query}.
+ *
+ * If there are aliases named as {@code key} or {@code value},
+ * then those will be used.
+ *
+ * Otherwise, the first column value is the key while the second one is the Map value.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ * @since 2.9.0
+ */
+public class MapResultTransformer implements ResultTransformer {
+
+ public static final String KEY_ALIAS = "map_key";
+
+ public static final String VALUE_ALIAS = "map_value";
+
+ private Map result = new HashMap<>();
+
+ /**
+ * Transform the tuple into a key/value pair.
+ *
+ * If there are aliases named as {@code key} or {@code value},
+ * then those will be used.
+ *
+ * Otherwise, the first column value is the key while the second one is the Map value.
+ *
+ * @param tuple tuple to be transformed to a key/value pair
+ * @param aliases column aliases
+ * @return unmodified tuple
+ */
+ @Override
+ public Object transformTuple(Object[] tuple, String[] aliases) {
+ int keyOrdinal = -1;
+ int valueOrdinal = -1;
+
+ for (int i = 0; i < aliases.length; i++) {
+ String alias = aliases[i];
+ if (KEY_ALIAS.equalsIgnoreCase(alias)) {
+ keyOrdinal = i;
+ } else if (VALUE_ALIAS.equalsIgnoreCase(alias)) {
+ valueOrdinal = i;
+ }
+ }
+
+ if (keyOrdinal >= 0) {
+ if (valueOrdinal < 0) {
+ throw new HibernateException(
+ new IllegalArgumentException("A key column alias was given but no value column alias was found!")
+ );
+ }
+ } else {
+ if (valueOrdinal >= 0) {
+ throw new HibernateException(
+ new IllegalArgumentException("A value column alias was given but no key column alias was found!")
+ );
+ } else {
+ keyOrdinal = 0;
+ valueOrdinal = 1;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ K key = (K) tuple[keyOrdinal];
+ @SuppressWarnings("unchecked")
+ V value = (V) tuple[valueOrdinal];
+ result.put(key, value);
+
+ return tuple;
+ }
+
+ /**
+ * Return the {@link Map} instead of the default {@link List}.
+ *
+ * @param tuples tuples
+ * @return the {@link Map} result set
+ */
+ @Override
+ public List transformList(List tuples) {
+ return Collections.singletonList(result);
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/SQLExtractor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/SQLExtractor.java
new file mode 100644
index 000000000..b4e02455e
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/query/SQLExtractor.java
@@ -0,0 +1,29 @@
+package com.vladmihalcea.hibernate.query;
+
+import com.vladmihalcea.hibernate.util.ReflectionUtils;
+import jakarta.persistence.Query;
+
+/**
+ * The {@link SQLExtractor} allows you to extract the
+ * underlying SQL query generated by a JPQL or JPA Criteria API query.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ * @since 2.9.11
+ */
+public class SQLExtractor {
+
+ protected SQLExtractor() {
+ }
+
+ /**
+ * Get the underlying SQL generated by the provided JPA query.
+ *
+ * @param query JPA query
+ * @return the underlying SQL generated by the provided JPA query
+ */
+ public static String from(Query query) {
+ return ReflectionUtils.invokeMethod(query, "getQueryString");
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/AbstractHibernateType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/AbstractHibernateType.java
new file mode 100644
index 000000000..b8b673fdf
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/AbstractHibernateType.java
@@ -0,0 +1,51 @@
+package com.vladmihalcea.hibernate.type;
+
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.AbstractSingleColumnStandardBasicType;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.JdbcType;
+
+/**
+ * Very convenient base class for implementing object types using Hibernate Java and SQL descriptors.
+ *
+ * @author Vlad Mihalcea
+ */
+public abstract class AbstractHibernateType extends AbstractSingleColumnStandardBasicType{
+
+ private final Configuration configuration;
+
+ /**
+ * Initialization constructor taking the {@link JdbcType} and {@link JavaType} objects,
+ * and using the default {@link Configuration} object.
+ *
+ * @param JdbcType the {@link JdbcType} to be used
+ * @param JavaType the {@link JavaType} to be used
+ */
+ protected AbstractHibernateType(
+ JdbcType JdbcType,
+ JavaType JavaType) {
+ super(JdbcType, JavaType);
+ this.configuration = Configuration.INSTANCE;
+ }
+
+ /**
+ * Initialization constructor taking the {@link JdbcType}, {@link JavaType},
+ * and {@link Configuration} objects.
+ *
+ * @param JdbcType the {@link JdbcType} to be used
+ * @param JavaType the {@link JavaType} to be used
+ * @param configuration custom {@link Configuration} object.
+ */
+ protected AbstractHibernateType(JdbcType JdbcType, JavaType JavaType, Configuration configuration) {
+ super(JdbcType, JavaType);
+ this.configuration = configuration;
+ }
+
+ /**
+ * Get the current {@link Configuration} object.
+ * @return the current {@link Configuration} object.
+ */
+ protected Configuration getConfiguration() {
+ return configuration;
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/DynamicMutableType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/DynamicMutableType.java
new file mode 100644
index 000000000..cbf051d7f
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/DynamicMutableType.java
@@ -0,0 +1,44 @@
+package com.vladmihalcea.hibernate.type;
+
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.JdbcType;
+import org.hibernate.usertype.DynamicParameterizedType;
+import org.hibernate.usertype.ParameterizedType;
+
+import java.sql.Types;
+import java.util.Properties;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class DynamicMutableType> extends MutableType implements DynamicParameterizedType {
+
+ private final Class returnedClass;
+
+ /**
+ * {@inheritDoc}
+ */
+ public DynamicMutableType(Class returnedClass, JDBC jdbcTypeDescriptor, JAVA javaTypeDescriptor) {
+ super(jdbcTypeDescriptor, javaTypeDescriptor);
+ this.returnedClass = returnedClass;
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ JAVA javaTypeDescriptor = getJavaTypeDescriptor();
+ if(javaTypeDescriptor instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) javaTypeDescriptor;
+ parameterizedType.setParameterValues(parameters);
+ }
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.OTHER;
+ }
+
+ @Override
+ public Class returnedClass() {
+ return returnedClass;
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/ImmutableType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/ImmutableType.java
new file mode 100644
index 000000000..d84cb6130
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/ImmutableType.java
@@ -0,0 +1,288 @@
+package com.vladmihalcea.hibernate.type;
+
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.HibernateException;
+import org.hibernate.MappingException;
+import org.hibernate.dialect.Dialect;
+import org.hibernate.engine.spi.Mapping;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.internal.util.collections.ArrayHelper;
+import org.hibernate.metamodel.model.domain.BasicDomainType;
+import org.hibernate.type.ForeignKeyDirection;
+import org.hibernate.type.Type;
+import org.hibernate.type.descriptor.java.IncomparableComparator;
+import org.hibernate.type.descriptor.jdbc.JdbcType;
+import org.hibernate.usertype.UserType;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+
+/**
+ * Very convenient base class for implementing immutable object types using Hibernate {@link UserType}.
+ *
+ * The {@link ImmutableType} implements the {@link Type} interface too, so you can pass all
+ * types extending the {@link ImmutableType} to the {@link org.hibernate.query.NativeQuery#addScalar(String, BasicDomainType)}
+ * method to fix the No Dialect mapping for JDBC type issues.
+ *
+ * @author Vlad Mihalcea
+ */
+public abstract class ImmutableType implements UserType, Type {
+
+ private final Configuration configuration;
+
+ private final Class clazz;
+
+ /**
+ * Initialization constructor taking the {@link Class}
+ * and using the default {@link Configuration} object.
+ *
+ * @param clazz the entity attribute {@link Class} type to be handled
+ */
+ protected ImmutableType(Class clazz) {
+ this.clazz = clazz;
+ this.configuration = Configuration.INSTANCE;
+ }
+
+ /**
+ * Initialization constructor taking the {@link Class} and {@link Configuration} objects.
+ *
+ * @param clazz the entity attribute {@link Class} type to be handled
+ * @param configuration custom {@link Configuration} object.
+ */
+ protected ImmutableType(Class clazz, Configuration configuration) {
+ this.clazz = clazz;
+ this.configuration = configuration;
+ }
+
+ /**
+ * Get the current {@link Configuration} object.
+ *
+ * @return the current {@link Configuration} object.
+ */
+ protected Configuration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * Get the column value from the JDBC {@link ResultSet}.
+ *
+ * @param rs JDBC {@link ResultSet}
+ * @param position database column position
+ * @param session current Hibernate {@link org.hibernate.Session}
+ * @param owner current Hibernate {@link SessionFactoryImplementor}
+ * @return column value
+ * @throws SQLException in case of failure
+ */
+ protected abstract T get(ResultSet rs, int position,
+ SharedSessionContractImplementor session, Object owner) throws SQLException;
+
+ /**
+ * Set the column value on the provided JDBC {@link PreparedStatement}.
+ *
+ * @param st JDBC {@link PreparedStatement}
+ * @param value database column value
+ * @param index database column index
+ * @param session current Hibernate {@link org.hibernate.Session}
+ * @throws SQLException in case of failure
+ */
+ protected abstract void set(PreparedStatement st, T value, int index,
+ SharedSessionContractImplementor session) throws SQLException;
+
+ /* Methods inherited from the {@link UserType} interface */
+
+ @Override
+ public T nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
+ return get(rs, position, session, owner);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index,
+ SharedSessionContractImplementor session) throws SQLException {
+ set(st, clazz.cast(value), index, session);
+ }
+
+ @Override
+ public Class returnedClass() {
+ return clazz;
+ }
+
+ @Override
+ public boolean equals(Object x, Object y) {
+ return (x == y) || (x != null && x.equals(y));
+ }
+
+ @Override
+ public int hashCode(Object x) {
+ return x.hashCode();
+ }
+
+ @Override
+ public T deepCopy(Object value) {
+ return (T) value;
+ }
+
+ @Override
+ public boolean isMutable() {
+ return false;
+ }
+
+ @Override
+ public Serializable disassemble(Object o) {
+ return (Serializable) o;
+ }
+
+ @Override
+ public T assemble(Serializable cached, Object owner) {
+ return (T) cached;
+ }
+
+ @Override
+ public T replace(Object o, Object target, Object owner) {
+ return (T) o;
+ }
+
+ /* Methods inherited from the {@link Type} interface */
+
+ @Override
+ public boolean isAssociationType() {
+ return false;
+ }
+
+ @Override
+ public boolean isCollectionType() {
+ return false;
+ }
+
+ @Override
+ public boolean isEntityType() {
+ return false;
+ }
+
+ @Override
+ public boolean isAnyType() {
+ return false;
+ }
+
+ @Override
+ public boolean isComponentType() {
+ return false;
+ }
+
+ @Override
+ public int getColumnSpan(Mapping mapping) throws MappingException {
+ return 1;
+ }
+
+ @Override
+ public Class getReturnedClass() {
+ return returnedClass();
+ }
+
+ @Override
+ public boolean isSame(Object x, Object y) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public boolean isEqual(Object x, Object y) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) throws HibernateException {
+ return equals(x, y);
+ }
+
+ @Override
+ public int getHashCode(Object x) throws HibernateException {
+ return hashCode(x);
+ }
+
+ @Override
+ public int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException {
+ return hashCode(x);
+ }
+
+ @Override
+ public int compare(Object x, Object y) {
+ return IncomparableComparator.INSTANCE.compare(x, y);
+ }
+
+ @Override
+ public final boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) {
+ return isDirty(old, current);
+ }
+
+ @Override
+ public final boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) {
+ return checkable[0] && isDirty(old, current);
+ }
+
+ protected final boolean isDirty(Object old, Object current) {
+ return !isSame(old, current);
+ }
+
+ @Override
+ public boolean isModified(Object dbState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException {
+ return isDirty(dbState, currentState);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+ set(st, returnedClass().cast(value), index, session);
+ }
+
+ @Override
+ public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public String getName() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public Object deepCopy(Object value, SessionFactoryImplementor factory) throws HibernateException {
+ return deepCopy(value);
+ }
+
+ @Override
+ public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return disassemble(value);
+ }
+
+ @Override
+ public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner) throws HibernateException {
+ return assemble(cached, session);
+ }
+
+ @Override
+ public void beforeAssemble(Serializable cached, SharedSessionContractImplementor session) {
+
+ }
+
+ @Override
+ public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache) throws HibernateException {
+ return replace(original, target, owner);
+ }
+
+ @Override
+ public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException {
+ return replace(original, target, owner);
+ }
+
+ @Override
+ public boolean[] toColumnNullness(Object value, Mapping mapping) {
+ return value == null ? ArrayHelper.FALSE : ArrayHelper.TRUE;
+ }
+
+ @Override
+ public int[] getSqlTypeCodes(Mapping mapping) throws MappingException {
+ return new int[]{getSqlType()};
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/MutableType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/MutableType.java
new file mode 100644
index 000000000..c209bdb7c
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/MutableType.java
@@ -0,0 +1,90 @@
+package com.vladmihalcea.hibernate.type;
+
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.HibernateException;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.JdbcType;
+import org.hibernate.usertype.UserType;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Very convenient base class for implementing immutable object types using Hibernate {@link UserType}.
+ *
+ * @author Vlad Mihalcea
+ */
+public abstract class MutableType> implements UserType {
+
+ private final JDBC jdbcTypeDescriptor;
+ private final JAVA javaTypeDescriptor;
+
+ /**
+ * Initialization constructor taking the {@link Class}
+ * and using the default {@link Configuration} object.
+ *
+ * @param jdbcTypeDescriptor the JDBC type descriptor
+ * @param javaTypeDescriptor the Java type descriptor
+ */
+ public MutableType(JDBC jdbcTypeDescriptor, JAVA javaTypeDescriptor) {
+ this.jdbcTypeDescriptor = jdbcTypeDescriptor;
+ this.javaTypeDescriptor = javaTypeDescriptor;
+ }
+
+ public JDBC getJdbcTypeDescriptor() {
+ return jdbcTypeDescriptor;
+ }
+
+ public JAVA getJavaTypeDescriptor() {
+ return javaTypeDescriptor;
+ }
+
+ @Override
+ public boolean equals(T x, T y) {
+ return javaTypeDescriptor.areEqual(x, y);
+ }
+
+ @Override
+ public int hashCode(T x) {
+ return javaTypeDescriptor.extractHashCode(x);
+ }
+
+ @Override
+ public T nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws
+ SQLException {
+ return jdbcTypeDescriptor.getExtractor(javaTypeDescriptor).extract(rs, position, session);
+ }
+
+ @Override
+ public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
+ jdbcTypeDescriptor.getBinder(javaTypeDescriptor).bind(st, (T) value, index, session);
+ }
+
+ @Override
+ public T deepCopy(T value) {
+ return javaTypeDescriptor.getMutabilityPlan().deepCopy(value);
+ }
+
+ @Override
+ public boolean isMutable() {
+ return javaTypeDescriptor.getMutabilityPlan().isMutable();
+ }
+
+ @Override
+ public Serializable disassemble(T value) {
+ return javaTypeDescriptor.getMutabilityPlan().disassemble(value, null);
+ }
+
+ @Override
+ public T assemble(Serializable cached, Object owner) {
+ return javaTypeDescriptor.getMutabilityPlan().assemble(cached, null);
+ }
+
+ @Override
+ public T replace(T detached, T managed, Object owner) {
+ return deepCopy(detached);
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/BooleanArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/BooleanArrayType.java
new file mode 100644
index 000000000..52e5c4709
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/BooleanArrayType.java
@@ -0,0 +1,52 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.BooleanArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.type.spi.TypeBootstrapContext;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+
+/**
+ * Maps a {@code boolean[]} array on a PostgreSQL ARRAY column type.
+ * For more details about how to use it, check out this article .
+ *
+ * @author jeet.choudhary7@gmail.com
+ * @version 2.9.13
+ */
+public class BooleanArrayType extends AbstractArrayType {
+
+ public static final BooleanArrayType INSTANCE = new BooleanArrayType();
+
+ public BooleanArrayType() {
+ super(
+ new BooleanArrayTypeDescriptor()
+ );
+ }
+
+ public BooleanArrayType(Configuration configuration) {
+ super(
+ new BooleanArrayTypeDescriptor(),
+ configuration
+ );
+ }
+
+ public BooleanArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public BooleanArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public String getName() {
+ return "boolean-array";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DateArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DateArrayType.java
new file mode 100644
index 000000000..2c8260371
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DateArrayType.java
@@ -0,0 +1,49 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.DateArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Date;
+import java.util.Properties;
+
+/**
+ * Maps an {@code Date[]} array on a PostgreSQL date[] ARRAY type. Multidimensional arrays are supported as well, as explained in this article .
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Guillaume Briand
+ */
+public class DateArrayType extends AbstractArrayType {
+
+ public static final DateArrayType INSTANCE = new DateArrayType();
+
+ public DateArrayType() {
+ super(
+ new DateArrayTypeDescriptor()
+ );
+ }
+
+ public DateArrayType(Configuration configuration) {
+ super(
+ new DateArrayTypeDescriptor(), configuration
+ );
+ }
+
+ public DateArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public DateArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "date-array";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DecimalArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DecimalArrayType.java
new file mode 100644
index 000000000..1b5cb7c7f
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DecimalArrayType.java
@@ -0,0 +1,51 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.DecimalArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.math.BigDecimal;
+import java.util.Properties;
+
+/**
+ * Maps a {@code decimal[]} array on a PostgreSQL ARRAY column type.
+ * For more details about how to use it, check out this article .
+ *
+ * @author Moritz Kobel
+ */
+public class DecimalArrayType extends AbstractArrayType {
+
+ public static final DecimalArrayType INSTANCE = new DecimalArrayType();
+
+ public DecimalArrayType() {
+ super(
+ new DecimalArrayTypeDescriptor()
+ );
+ }
+
+ public DecimalArrayType(Configuration configuration) {
+ super(
+ new DecimalArrayTypeDescriptor(),
+ configuration
+ );
+ }
+
+ public DecimalArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public DecimalArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public String getName() {
+ return "decimal-array";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DoubleArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DoubleArrayType.java
new file mode 100644
index 000000000..0d726ef3c
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/DoubleArrayType.java
@@ -0,0 +1,52 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.DoubleArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+
+/**
+ * Maps a {@code double[]} array on a PostgreSQL ARRAY type. Multidimensional arrays are supported as well, as explained in this article .
+ *
+ * For more details about how to use it, check out this
+ * article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class DoubleArrayType extends AbstractArrayType {
+
+ public static final DoubleArrayType INSTANCE = new DoubleArrayType();
+
+ public DoubleArrayType() {
+ super(
+ new DoubleArrayTypeDescriptor()
+ );
+ }
+
+ public DoubleArrayType(Configuration configuration) {
+ super(
+ new DoubleArrayTypeDescriptor(),
+ configuration
+ );
+ }
+
+ public DoubleArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public DoubleArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public String getName() {
+ return "double-array";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/EnumArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/EnumArrayType.java
new file mode 100644
index 000000000..f7ad20bbd
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/EnumArrayType.java
@@ -0,0 +1,70 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.array.internal.EnumArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.annotations.Type;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.lang.annotation.Annotation;
+import java.util.Properties;
+
+/**
+ * Maps an {@code Enum[]} array on a database ARRAY type. Multidimensional arrays are supported as well, as explained in this article .
+ *
+ * The {@code SQL_ARRAY_TYPE} parameter is used to define the enum type name in the database.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Nazir El-Kayssi
+ * @author Vlad Mihalcea
+ */
+public class EnumArrayType extends AbstractArrayType {
+
+ public static final EnumArrayType INSTANCE = new EnumArrayType();
+ private static final String DEFAULT_TYPE_NAME = "%s_enum_array_type";
+
+ private String name;
+
+ public EnumArrayType() {
+ super(
+ new EnumArrayTypeDescriptor()
+ );
+ }
+
+ public EnumArrayType(Configuration configuration) {
+ super(
+ new EnumArrayTypeDescriptor(),
+ configuration
+ );
+ }
+
+ public EnumArrayType(Class arrayClass, String sqlArrayType) {
+ this();
+ Properties parameters = new Properties();
+ parameters.setProperty(SQL_ARRAY_TYPE, sqlArrayType);
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public EnumArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ DynamicParameterizedType.ParameterType parameterType = (ParameterType) parameters.get(DynamicParameterizedType.PARAMETER_TYPE);
+ Annotation[] annotations = parameterType.getAnnotationsMethod();
+ if (name == null) {
+ name = String.format(DEFAULT_TYPE_NAME, parameters.getProperty(SQL_ARRAY_TYPE));
+ }
+ ((AbstractArrayTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters);
+ }
+
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/IntArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/IntArrayType.java
new file mode 100644
index 000000000..ec255319e
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/IntArrayType.java
@@ -0,0 +1,48 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.IntArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+
+/**
+ * Maps an {@code int[]} array on a PostgreSQL ARRAY type. Multidimensional arrays are supported as well, as explained in this article .
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class IntArrayType extends AbstractArrayType {
+
+ public static final IntArrayType INSTANCE = new IntArrayType();
+
+ public IntArrayType() {
+ super(
+ new IntArrayTypeDescriptor()
+ );
+ }
+
+ public IntArrayType(Configuration configuration) {
+ super(
+ new IntArrayTypeDescriptor(), configuration
+ );
+ }
+
+ public IntArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public IntArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "int-array";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/ListArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/ListArrayType.java
new file mode 100644
index 000000000..585dbbb99
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/ListArrayType.java
@@ -0,0 +1,37 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.ListArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+
+/**
+ * Maps an {@link java.util.List} entity attribute on a PostgreSQL ARRAY column type.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class ListArrayType extends AbstractArrayType {
+
+ public static final ListArrayType INSTANCE = new ListArrayType();
+
+ public ListArrayType() {
+ super(
+ new ListArrayTypeDescriptor()
+ );
+ }
+
+ public ListArrayType(Configuration configuration) {
+ super(
+ new ListArrayTypeDescriptor(), configuration
+ );
+ }
+
+ public ListArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "list-array";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LocalDateArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LocalDateArrayType.java
new file mode 100644
index 000000000..80e9eeb28
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LocalDateArrayType.java
@@ -0,0 +1,54 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.LocalDateArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+
+/**
+ * Maps a {@code java.Time.LocalDate[]} array on a PostgreSQL date[] ARRAY type. Multidimensional arrays are
+ * supported as well, as
+ * explained in this article .
+ *
+ * For more details about how to use it, check out
+ * this
+ * article on vladmihalcea.com .
+ *
+ * @author Andrew Lazarus, based on DateArrayType by Guillaume Briand
+ */
+
+public class LocalDateArrayType extends AbstractArrayType {
+
+ public static final com.vladmihalcea.hibernate.type.array.LocalDateArrayType INSTANCE =
+ new com.vladmihalcea.hibernate.type.array.LocalDateArrayType();
+
+ public LocalDateArrayType() {
+ super(
+ new LocalDateArrayTypeDescriptor()
+ );
+ }
+
+ public LocalDateArrayType(Configuration configuration) {
+ super(
+ new LocalDateArrayTypeDescriptor(), configuration
+ );
+ }
+
+ public LocalDateArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public LocalDateArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "localdate-array";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LocalDateTimeArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LocalDateTimeArrayType.java
new file mode 100644
index 000000000..624fbade0
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LocalDateTimeArrayType.java
@@ -0,0 +1,54 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.LocalDateTimeArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+
+/**
+ * Maps a {@code java.Time.LocalDateTime[]} array on a PostgreSQL timestamp[] ARRAY type. Multidimensional arrays are
+ * supported as well, as
+ * explained in this article .
+ *
+ * For more details about how to use it, check out
+ * this
+ * article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+
+public class LocalDateTimeArrayType extends AbstractArrayType {
+
+ public static final LocalDateTimeArrayType INSTANCE =
+ new LocalDateTimeArrayType();
+
+ public LocalDateTimeArrayType() {
+ super(
+ new LocalDateTimeArrayTypeDescriptor()
+ );
+ }
+
+ public LocalDateTimeArrayType(Configuration configuration) {
+ super(
+ new LocalDateTimeArrayTypeDescriptor(), configuration
+ );
+ }
+
+ public LocalDateTimeArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public LocalDateTimeArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "localdatetime-array";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LongArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LongArrayType.java
new file mode 100644
index 000000000..cf8dcc2fc
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/LongArrayType.java
@@ -0,0 +1,51 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.LongArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+
+/**
+ * Maps an {@code long[]} array on a PostgreSQL ARRAY type. Multidimensional arrays are supported as well, as explained in this article .
+ *
+ * For more details about how to use it, check out this
+ * article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class LongArrayType extends AbstractArrayType {
+
+ public static final LongArrayType INSTANCE = new LongArrayType();
+
+ public LongArrayType() {
+ super(
+ new LongArrayTypeDescriptor()
+ );
+ }
+
+ public LongArrayType(Configuration configuration) {
+ super(
+ new LongArrayTypeDescriptor(),
+ configuration
+ );
+ }
+
+ public LongArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public LongArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public String getName() {
+ return "long-array";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/StringArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/StringArrayType.java
new file mode 100644
index 000000000..1ca588cfb
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/StringArrayType.java
@@ -0,0 +1,49 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.StringArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+
+/**
+ * Maps an {@code String[]} array on a PostgreSQL ARRAY type. Multidimensional arrays are supported as well, as explained in this article .
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class StringArrayType extends AbstractArrayType {
+
+ public static final StringArrayType INSTANCE = new StringArrayType();
+
+ public StringArrayType() {
+ super(
+ new StringArrayTypeDescriptor()
+ );
+ }
+
+ public StringArrayType(Configuration configuration) {
+ super(
+ new StringArrayTypeDescriptor(),
+ configuration
+ );
+ }
+
+ public StringArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public StringArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "string-array";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/TimestampArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/TimestampArrayType.java
new file mode 100644
index 000000000..b742ed2ba
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/TimestampArrayType.java
@@ -0,0 +1,49 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.TimestampArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Date;
+import java.util.Properties;
+
+/**
+ * Maps an {@code Date[]} array on a PostgreSQL timestamp[] ARRAY type. Multidimensional arrays are supported as well, as explained in this article .
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class TimestampArrayType extends AbstractArrayType {
+
+ public static final TimestampArrayType INSTANCE = new TimestampArrayType();
+
+ public TimestampArrayType() {
+ super(
+ new TimestampArrayTypeDescriptor()
+ );
+ }
+
+ public TimestampArrayType(Configuration configuration) {
+ super(
+ new TimestampArrayTypeDescriptor(), configuration
+ );
+ }
+
+ public TimestampArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public TimestampArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "timestamp-array";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/UUIDArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/UUIDArrayType.java
new file mode 100644
index 000000000..503faffd2
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/UUIDArrayType.java
@@ -0,0 +1,50 @@
+package com.vladmihalcea.hibernate.type.array;
+
+import com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType;
+import com.vladmihalcea.hibernate.type.array.internal.UUIDArrayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ParameterizedParameterType;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+import java.util.UUID;
+
+/**
+ * Maps an {@code UUID[]} array on a PostgreSQL ARRAY type. Multidimensional arrays are supported as well, as explained in this article .
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Rafael Acevedo
+ */
+public class UUIDArrayType extends AbstractArrayType {
+
+ public static final UUIDArrayType INSTANCE = new UUIDArrayType();
+
+ public UUIDArrayType() {
+ super(
+ new UUIDArrayTypeDescriptor()
+ );
+ }
+
+ public UUIDArrayType(Configuration configuration) {
+ super(
+ new UUIDArrayTypeDescriptor(),
+ configuration
+ );
+ }
+
+ public UUIDArrayType(Class arrayClass) {
+ this();
+ Properties parameters = new Properties();
+ parameters.put(DynamicParameterizedType.PARAMETER_TYPE, new ParameterizedParameterType(arrayClass));
+ setParameterValues(parameters);
+ }
+
+ public UUIDArrayType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "uuid-array";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/AbstractArrayType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/AbstractArrayType.java
new file mode 100644
index 000000000..3bc3b917f
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/AbstractArrayType.java
@@ -0,0 +1,44 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.util.Properties;
+
+/**
+ * Base class for all ARRAY types.
+ *
+ * @author Vlad Mihalcea
+ */
+public abstract class AbstractArrayType
+ extends AbstractHibernateType
+ implements DynamicParameterizedType {
+
+ public static final String SQL_ARRAY_TYPE = "sql_array_type";
+
+ public AbstractArrayType(AbstractArrayTypeDescriptor arrayTypeDescriptor) {
+ super(
+ ArraySqlTypeDescriptor.INSTANCE,
+ arrayTypeDescriptor
+ );
+ }
+
+ public AbstractArrayType(AbstractArrayTypeDescriptor arrayTypeDescriptor, Configuration configuration) {
+ super(
+ ArraySqlTypeDescriptor.INSTANCE,
+ arrayTypeDescriptor,
+ configuration
+ );
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ ((AbstractArrayTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters);
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/AbstractArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/AbstractArrayTypeDescriptor.java
new file mode 100644
index 000000000..df3bf187c
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/AbstractArrayTypeDescriptor.java
@@ -0,0 +1,102 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import org.hibernate.HibernateException;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+import org.hibernate.type.descriptor.java.MutabilityPlan;
+import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.sql.Array;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Properties;
+
+import static com.vladmihalcea.hibernate.type.array.internal.AbstractArrayType.SQL_ARRAY_TYPE;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public abstract class AbstractArrayTypeDescriptor
+ extends AbstractClassJavaType implements DynamicParameterizedType {
+
+ private Class arrayObjectClass;
+
+ private String sqlArrayType;
+
+ public AbstractArrayTypeDescriptor(Class arrayObjectClass) {
+ this(arrayObjectClass, (MutabilityPlan) new MutableMutabilityPlan() {
+ @Override
+ protected T deepCopyNotNull(Object value) {
+ return ArrayUtil.deepCopy(value);
+ }
+ });
+ }
+
+ protected AbstractArrayTypeDescriptor(Class arrayObjectClass, MutabilityPlan mutableMutabilityPlan) {
+ super(arrayObjectClass, mutableMutabilityPlan);
+ this.arrayObjectClass = arrayObjectClass;
+ }
+
+ public Class getArrayObjectClass() {
+ return arrayObjectClass;
+ }
+
+ public void setArrayObjectClass(Class arrayObjectClass) {
+ this.arrayObjectClass = arrayObjectClass;
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ if (parameters.containsKey(PARAMETER_TYPE)) {
+ arrayObjectClass = (Class) ((ParameterType) parameters.get(PARAMETER_TYPE)).getReturnedClass();
+ }
+ sqlArrayType = parameters.getProperty(SQL_ARRAY_TYPE);
+ }
+
+ @Override
+ public boolean areEqual(Object one, Object another) {
+ if (one == another) {
+ return true;
+ }
+ if (one == null || another == null) {
+ return false;
+ }
+ return ArrayUtil.isEquals(one, another);
+ }
+
+ @Override
+ public String toString(Object value) {
+ return Arrays.deepToString(ArrayUtil.wrapArray(value));
+ }
+
+ @Override
+ public String extractLoggableRepresentation(T value) {
+ return (value == null) ? "null" : toString(value);
+ }
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public X unwrap(T value, Class type, WrapperOptions options) {
+ return (X) ArrayUtil.wrapArray(value);
+ }
+
+ @Override
+ public T wrap(X value, WrapperOptions options) {
+ if (value instanceof Array) {
+ Array array = (Array) value;
+ try {
+ return ArrayUtil.unwrapArray((Object[]) array.getArray(), arrayObjectClass);
+ } catch (SQLException e) {
+ throw new HibernateException(
+ new IllegalArgumentException(e)
+ );
+ }
+ }
+ return (T) value;
+ }
+
+ protected String getSqlArrayType() {
+ return sqlArrayType;
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ArraySqlTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ArraySqlTypeDescriptor.java
new file mode 100644
index 000000000..873974bb4
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ArraySqlTypeDescriptor.java
@@ -0,0 +1,67 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import org.hibernate.type.descriptor.ValueBinder;
+import org.hibernate.type.descriptor.ValueExtractor;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.BasicBinder;
+import org.hibernate.type.descriptor.jdbc.BasicExtractor;
+import org.hibernate.type.descriptor.jdbc.JdbcType;
+
+import java.sql.*;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class ArraySqlTypeDescriptor implements JdbcType {
+
+ public static final ArraySqlTypeDescriptor INSTANCE = new ArraySqlTypeDescriptor();
+
+ @Override
+ public int getJdbcTypeCode() {
+ return Types.ARRAY;
+ }
+
+ @Override
+ public ValueBinder getBinder(final JavaType JavaType) {
+ return new BasicBinder(JavaType, this) {
+ @Override
+ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
+ AbstractArrayTypeDescriptor abstractArrayTypeDescriptor = (AbstractArrayTypeDescriptor) JavaType;
+ st.setArray(index, st.getConnection().createArrayOf(
+ abstractArrayTypeDescriptor.getSqlArrayType(),
+ abstractArrayTypeDescriptor.unwrap(value, Object[].class, options)
+ ));
+ }
+
+ @Override
+ protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
+ throws SQLException {
+ throw new UnsupportedOperationException("Binding by name is not supported!");
+ }
+ };
+ }
+
+ @Override
+ public ValueExtractor getExtractor(final JavaType JavaType) {
+ return new BasicExtractor(JavaType, this) {
+ @Override
+ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
+ return JavaType.wrap(rs.getArray(paramIndex), options);
+ }
+
+ @Override
+ protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
+ return JavaType.wrap(statement.getArray(index), options);
+ }
+
+ @Override
+ protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
+ return JavaType.wrap(statement.getArray(name), options);
+ }
+
+
+ };
+ }
+
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ArrayUtil.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ArrayUtil.java
new file mode 100644
index 000000000..c4021b9e2
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ArrayUtil.java
@@ -0,0 +1,376 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import java.lang.reflect.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * ArrayUtil
- Array utilities holder.
+ *
+ * @author Vlad Mihalcea
+ */
+public class ArrayUtil {
+
+ /**
+ * Clone an array.
+ *
+ * @param originalArray original array
+ * @param array element type
+ * @return cloned array
+ */
+ public static T deepCopy(Object originalArray) {
+ Class arrayClass = originalArray.getClass();
+
+ if (boolean[].class.equals(arrayClass)) {
+ boolean[] array = (boolean[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ } else if (byte[].class.equals(arrayClass)) {
+ byte[] array = (byte[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ } else if (short[].class.equals(arrayClass)) {
+ short[] array = (short[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ } else if (int[].class.equals(arrayClass)) {
+ int[] array = (int[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ } else if (long[].class.equals(arrayClass)) {
+ long[] array = (long[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ } else if (float[].class.equals(arrayClass)) {
+ float[] array = (float[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ } else if (double[].class.equals(arrayClass)) {
+ double[] array = (double[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ } else if (char[].class.equals(arrayClass)) {
+ char[] array = (char[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ } else {
+ Object[] array = (Object[]) originalArray;
+ return (T) Arrays.copyOf(array, array.length);
+ }
+ }
+
+ /**
+ * Wrap a given array so that primitives become wrapper objects.
+ *
+ * @param originalArray original array
+ * @return wrapped array
+ */
+ public static Object[] wrapArray(Object originalArray) {
+ Class arrayClass = originalArray.getClass();
+
+ if (boolean[].class.equals(arrayClass)) {
+ boolean[] fromArray = (boolean[]) originalArray;
+ Boolean[] array = new Boolean[fromArray.length];
+ for (int i = 0; i < fromArray.length; i++) {
+ array[i] = fromArray[i];
+ }
+ return array;
+ } else if (byte[].class.equals(arrayClass)) {
+ byte[] fromArray = (byte[]) originalArray;
+ Byte[] array = new Byte[fromArray.length];
+ for (int i = 0; i < fromArray.length; i++) {
+ array[i] = fromArray[i];
+ }
+ return array;
+ } else if (short[].class.equals(arrayClass)) {
+ short[] fromArray = (short[]) originalArray;
+ Short[] array = new Short[fromArray.length];
+ for (int i = 0; i < fromArray.length; i++) {
+ array[i] = fromArray[i];
+ }
+ return array;
+ } else if (int[].class.equals(arrayClass)) {
+ int[] fromArray = (int[]) originalArray;
+ Integer[] array = new Integer[fromArray.length];
+ for (int i = 0; i < fromArray.length; i++) {
+ array[i] = fromArray[i];
+ }
+ return array;
+ } else if (long[].class.equals(arrayClass)) {
+ long[] fromArray = (long[]) originalArray;
+ Long[] array = new Long[fromArray.length];
+ for (int i = 0; i < fromArray.length; i++) {
+ array[i] = fromArray[i];
+ }
+ return array;
+ } else if (float[].class.equals(arrayClass)) {
+ float[] fromArray = (float[]) originalArray;
+ Float[] array = new Float[fromArray.length];
+ for (int i = 0; i < fromArray.length; i++) {
+ array[i] = fromArray[i];
+ }
+ return array;
+ } else if (double[].class.equals(arrayClass)) {
+ double[] fromArray = (double[]) originalArray;
+ Double[] array = new Double[fromArray.length];
+ for (int i = 0; i < fromArray.length; i++) {
+ array[i] = fromArray[i];
+ }
+ return array;
+ } else if (char[].class.equals(arrayClass)) {
+ char[] fromArray = (char[]) originalArray;
+ Character[] array = new Character[fromArray.length];
+ for (int i = 0; i < fromArray.length; i++) {
+ array[i] = fromArray[i];
+ }
+ return array;
+ } else if (originalArray instanceof List) {
+ return ((List) originalArray).toArray();
+ } else {
+ return (Object[]) originalArray;
+ }
+ }
+
+ /**
+ * Unwrap {@link Object[]} array to an array of the provided type
+ *
+ * @param originalArray original array
+ * @param arrayClass array class
+ * @param array element type
+ * @return unwrapped array
+ */
+ public static T unwrapArray(Object[] originalArray, Class arrayClass) {
+
+ if (boolean[].class.equals(arrayClass)) {
+ boolean[] array = new boolean[originalArray.length];
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = originalArray[i] != null ? (Boolean) originalArray[i] : Boolean.FALSE;
+ }
+ return (T) array;
+ } else if (byte[].class.equals(arrayClass)) {
+ byte[] array = new byte[originalArray.length];
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = originalArray[i] != null ? (Byte) originalArray[i] : 0;
+ }
+ return (T) array;
+ } else if (short[].class.equals(arrayClass)) {
+ short[] array = new short[originalArray.length];
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = originalArray[i] != null ? (Short) originalArray[i] : 0;
+ }
+ return (T) array;
+ } else if (int[].class.equals(arrayClass)) {
+ int[] array = new int[originalArray.length];
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = originalArray[i] != null ? (Integer) originalArray[i] : 0;
+ }
+ return (T) array;
+ } else if (long[].class.equals(arrayClass)) {
+ long[] array = new long[originalArray.length];
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = originalArray[i] != null ? (Long) originalArray[i] : 0L;
+ }
+ return (T) array;
+ } else if (float[].class.equals(arrayClass)) {
+ float[] array = new float[originalArray.length];
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = originalArray[i] != null ? (Float) originalArray[i] : 0f;
+ }
+ return (T) array;
+ } else if (double[].class.equals(arrayClass)) {
+ double[] array = new double[originalArray.length];
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = originalArray[i] != null ? (Double) originalArray[i] : 0d;
+ }
+ return (T) array;
+ } else if (char[].class.equals(arrayClass)) {
+ char[] array = new char[originalArray.length];
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = originalArray[i] != null ? (Character) originalArray[i] : 0;
+ }
+ return (T) array;
+ } else if (Enum[].class.isAssignableFrom(arrayClass)) {
+ T array = arrayClass.cast(Array.newInstance(arrayClass.getComponentType(), originalArray.length));
+ for (int i = 0; i < originalArray.length; i++) {
+ Object objectValue = originalArray[i];
+ if (objectValue != null) {
+ String stringValue = (objectValue instanceof String) ? (String) objectValue : String.valueOf(objectValue);
+ objectValue = Enum.valueOf((Class) arrayClass.getComponentType(), stringValue);
+ }
+ Array.set(array, i, objectValue);
+ }
+ return array;
+ } else if (java.time.LocalDate[].class.equals(arrayClass) && java.sql.Date[].class.equals(originalArray.getClass())) {
+ // special case because conversion is neither with ctor nor valueOf
+ Object[] array = (Object[]) Array.newInstance(java.time.LocalDate.class, originalArray.length);
+ for (int i = 0; i < array.length; ++i) {
+ array[i] = originalArray[i] != null ? ((java.sql.Date) originalArray[i]).toLocalDate() : null;
+ }
+ return (T) array;
+ } else if (java.time.LocalDateTime[].class.equals(arrayClass) && java.sql.Timestamp[].class.equals(originalArray.getClass())) {
+ // special case because conversion is neither with ctor nor valueOf
+ Object[] array = (Object[]) Array.newInstance(java.time.LocalDateTime.class, originalArray.length);
+ for (int i = 0; i < array.length; ++i) {
+ array[i] = originalArray[i] != null ? ((java.sql.Timestamp) originalArray[i]).toLocalDateTime() : null;
+ }
+ return (T) array;
+ } else if(arrayClass.getComponentType() != null && arrayClass.getComponentType().isArray()) {
+ int arrayLength = originalArray.length;
+ Object[] array = (Object[]) Array.newInstance(arrayClass.getComponentType(), arrayLength);
+ if (arrayLength > 0) {
+ for (int i = 0; i < originalArray.length; i++) {
+ array[i] = unwrapArray((Object[]) originalArray[i], arrayClass.getComponentType());
+ }
+ }
+ return (T) array;
+ } else {
+ if (arrayClass.isInstance(originalArray)) {
+ return (T) originalArray;
+ } else {
+ return (T) Arrays.copyOf(originalArray, originalArray.length, (Class) arrayClass);
+ }
+ }
+ }
+
+ /**
+ * Create array from its {@link String} representation.
+ *
+ * @param string string representation
+ * @param arrayClass array class
+ * @param array element type
+ * @return array
+ */
+ public static T fromString(String string, Class arrayClass) {
+ String stringArray = string.replaceAll("[\\[\\]]", "");
+ String[] tokens = stringArray.split(",");
+
+ int length = tokens.length;
+
+ if (boolean[].class.equals(arrayClass)) {
+ boolean[] array = new boolean[length];
+ for (int i = 0; i < tokens.length; i++) {
+ array[i] = Boolean.valueOf(tokens[i]);
+ }
+ return (T) array;
+ } else if (byte[].class.equals(arrayClass)) {
+ byte[] array = new byte[length];
+ for (int i = 0; i < tokens.length; i++) {
+ array[i] = Byte.valueOf(tokens[i]);
+ }
+ return (T) array;
+ } else if (short[].class.equals(arrayClass)) {
+ short[] array = new short[length];
+ for (int i = 0; i < tokens.length; i++) {
+ array[i] = Short.valueOf(tokens[i]);
+ }
+ return (T) array;
+ } else if (int[].class.equals(arrayClass)) {
+ int[] array = new int[length];
+ for (int i = 0; i < tokens.length; i++) {
+ array[i] = Integer.valueOf(tokens[i]);
+ }
+ return (T) array;
+ } else if (long[].class.equals(arrayClass)) {
+ long[] array = new long[length];
+ for (int i = 0; i < tokens.length; i++) {
+ array[i] = Long.valueOf(tokens[i]);
+ }
+ return (T) array;
+ } else if (float[].class.equals(arrayClass)) {
+ float[] array = new float[length];
+ for (int i = 0; i < tokens.length; i++) {
+ array[i] = Float.valueOf(tokens[i]);
+ }
+ return (T) array;
+ } else if (double[].class.equals(arrayClass)) {
+ double[] array = new double[length];
+ for (int i = 0; i < tokens.length; i++) {
+ array[i] = Double.valueOf(tokens[i]);
+ }
+ return (T) array;
+ } else if (char[].class.equals(arrayClass)) {
+ char[] array = new char[length];
+ for (int i = 0; i < tokens.length; i++) {
+ array[i] = tokens[i].length() > 0 ? tokens[i].charAt(0) : Character.MIN_VALUE;
+ }
+ return (T) array;
+ } else {
+ return (T) tokens;
+ }
+ }
+
+ /**
+ * Check if two arrays are equal.
+ *
+ * @param firstArray first array
+ * @param secondArray second array
+ * @return arrays are equal
+ */
+ public static boolean isEquals(Object firstArray, Object secondArray) {
+ if (firstArray.getClass() != secondArray.getClass()) {
+ return false;
+ }
+ Class arrayClass = firstArray.getClass();
+
+ if (boolean[].class.equals(arrayClass)) {
+ return Arrays.equals((boolean[]) firstArray, (boolean[]) secondArray);
+ } else if (byte[].class.equals(arrayClass)) {
+ return Arrays.equals((byte[]) firstArray, (byte[]) secondArray);
+ } else if (short[].class.equals(arrayClass)) {
+ return Arrays.equals((short[]) firstArray, (short[]) secondArray);
+ } else if (int[].class.equals(arrayClass)) {
+ return Arrays.equals((int[]) firstArray, (int[]) secondArray);
+ } else if (long[].class.equals(arrayClass)) {
+ return Arrays.equals((long[]) firstArray, (long[]) secondArray);
+ } else if (float[].class.equals(arrayClass)) {
+ return Arrays.equals((float[]) firstArray, (float[]) secondArray);
+ } else if (double[].class.equals(arrayClass)) {
+ return Arrays.equals((double[]) firstArray, (double[]) secondArray);
+ } else if (char[].class.equals(arrayClass)) {
+ return Arrays.equals((char[]) firstArray, (char[]) secondArray);
+ } else {
+ return Arrays.equals((Object[]) firstArray, (Object[]) secondArray);
+ }
+ }
+
+ /**
+ * Get the array class for the provided array element class.
+ *
+ * @param arrayElementClass array element class
+ * @param array element type
+ * @return array class
+ */
+ public static Class toArrayClass(Class arrayElementClass) {
+
+ if (boolean.class.equals(arrayElementClass)) {
+ return (Class) boolean[].class;
+ } else if (byte.class.equals(arrayElementClass)) {
+ return (Class) byte[].class;
+ } else if (short.class.equals(arrayElementClass)) {
+ return (Class) short[].class;
+ } else if (int.class.equals(arrayElementClass)) {
+ return (Class) int[].class;
+ } else if (long.class.equals(arrayElementClass)) {
+ return (Class) long[].class;
+ } else if (float.class.equals(arrayElementClass)) {
+ return (Class) float[].class;
+ } else if (double[].class.equals(arrayElementClass)) {
+ return (Class) double[].class;
+ } else if (char[].class.equals(arrayElementClass)) {
+ return (Class) char[].class;
+ } else {
+ Object array = Array.newInstance(arrayElementClass, 0);
+ return (Class) array.getClass();
+ }
+ }
+
+ /**
+ * Transforms an array to a {@link List}. The reason why {@link Arrays#asList(Object[])}
+ * is not used is because on Java 6 it wraps the {@link List} so we end up
+ * with two nested {@link List} objects.
+ *
+ * @param array array to transform
+ * @param array element type
+ * @return the {@link List} representation of the array
+ */
+ public static List asList(T[] array) {
+ List list = new ArrayList(array.length);
+ for (int i = 0; i < array.length; i++) {
+ list.add(i, array[i]);
+ }
+ return list;
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/BooleanArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/BooleanArrayTypeDescriptor.java
new file mode 100644
index 000000000..acf0fbaf8
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/BooleanArrayTypeDescriptor.java
@@ -0,0 +1,18 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+/**
+ * @author jeet.choudhary7@gmail.com
+ * @version 2.9.13
+ */
+public class BooleanArrayTypeDescriptor extends AbstractArrayTypeDescriptor {
+
+ public BooleanArrayTypeDescriptor() {
+ super(boolean[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ String sqlArrayType = super.getSqlArrayType();
+ return sqlArrayType != null ? sqlArrayType : "boolean";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DateArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DateArrayTypeDescriptor.java
new file mode 100644
index 000000000..411bf1557
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DateArrayTypeDescriptor.java
@@ -0,0 +1,18 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import java.util.Date;
+
+/**
+ * @author Guillaume Briand
+ */
+public class DateArrayTypeDescriptor extends AbstractArrayTypeDescriptor {
+
+ public DateArrayTypeDescriptor() {
+ super(Date[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return "date";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DecimalArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DecimalArrayTypeDescriptor.java
new file mode 100644
index 000000000..f2d0392b5
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DecimalArrayTypeDescriptor.java
@@ -0,0 +1,18 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import java.math.BigDecimal;
+
+/**
+ * @author Moritz Kobel
+ */
+public class DecimalArrayTypeDescriptor extends AbstractArrayTypeDescriptor {
+
+ public DecimalArrayTypeDescriptor() {
+ super(BigDecimal[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return "decimal";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DoubleArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DoubleArrayTypeDescriptor.java
new file mode 100644
index 000000000..e14d810ab
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/DoubleArrayTypeDescriptor.java
@@ -0,0 +1,18 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class DoubleArrayTypeDescriptor
+ extends AbstractArrayTypeDescriptor {
+
+ public DoubleArrayTypeDescriptor() {
+ super(double[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ String sqlArrayType = super.getSqlArrayType();
+ return sqlArrayType != null ? sqlArrayType : "float8";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/EnumArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/EnumArrayTypeDescriptor.java
new file mode 100644
index 000000000..56777679c
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/EnumArrayTypeDescriptor.java
@@ -0,0 +1,30 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import com.vladmihalcea.hibernate.type.array.EnumArrayType;
+
+import java.util.Properties;
+
+/**
+ * @author Nazir El-Kayssi
+ * @author Vlad Mihalcea
+ */
+public class EnumArrayTypeDescriptor
+ extends AbstractArrayTypeDescriptor {
+
+ private String sqlArrayType;
+
+ public EnumArrayTypeDescriptor() {
+ super(Enum[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return sqlArrayType;
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ sqlArrayType = parameters.getProperty(EnumArrayType.SQL_ARRAY_TYPE);
+ super.setParameterValues(parameters);
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/IntArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/IntArrayTypeDescriptor.java
new file mode 100644
index 000000000..92c0ea26d
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/IntArrayTypeDescriptor.java
@@ -0,0 +1,17 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class IntArrayTypeDescriptor
+ extends AbstractArrayTypeDescriptor {
+
+ public IntArrayTypeDescriptor() {
+ super(int[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return "integer";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ListArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ListArrayTypeDescriptor.java
new file mode 100644
index 000000000..cd4e5e031
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/ListArrayTypeDescriptor.java
@@ -0,0 +1,147 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import com.vladmihalcea.hibernate.util.ReflectionUtils;
+import org.hibernate.SharedSessionContract;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.io.Serializable;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.*;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class ListArrayTypeDescriptor extends AbstractArrayTypeDescriptor {
+
+ private String sqlArrayType;
+
+ public ListArrayTypeDescriptor() {
+ super(Object.class, new MutableMutabilityPlan() {
+ @Override
+ protected Object deepCopyNotNull(Object value) {
+ if (value instanceof List) {
+ Object[] array = ((List) value).toArray();
+ return ArrayUtil.asList((Object[]) ArrayUtil.deepCopy(array));
+ } else if (value.getClass().isArray()) {
+ Object[] array = (Object[]) value;
+ return ArrayUtil.deepCopy(array);
+ } else {
+ throw new UnsupportedOperationException("The provided " + value + " is not a List!");
+ }
+ }
+
+ @Override
+ public Object assemble(Serializable cached, SharedSessionContract session) {
+ if (cached != null && cached.getClass().isArray()) {
+ Object[] array = (Object[]) cached;
+ return Arrays.asList(array);
+ }
+ return super.assemble(cached, session);
+ }
+ });
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return sqlArrayType;
+ }
+
+ @Override
+ public Object unwrap(Object value, Class type, WrapperOptions options) {
+ if (value instanceof Object[]) {
+ return value;
+ } else if (value instanceof List) {
+ return super.unwrap(((List) value).toArray(), type, options);
+ } else {
+ throw new UnsupportedOperationException("The provided " + value + " is not a Object[] or List!");
+ }
+ }
+
+ @Override
+ public Object wrap(Object value, WrapperOptions options) {
+ Object wrappedObject = super.wrap(value, options);
+ List list = null;
+ if (wrappedObject != null) {
+ list = new ArrayList<>();
+ if (wrappedObject instanceof Object[]) {
+ Object[] wrappedArray = (Object[]) wrappedObject;
+ Collections.addAll(list, wrappedArray);
+ } else {
+ throw new UnsupportedOperationException("The wrapped object " + value + " is not an Object[]!");
+ }
+ }
+ return list;
+ }
+
+ @Override
+ public boolean areEqual(Object one, Object another) {
+ if (one == another) {
+ return true;
+ }
+ if (one == null || another == null) {
+ return false;
+ }
+ if (one instanceof List && another instanceof List) {
+ return ArrayUtil.isEquals(((List) one).toArray(), ((List) another).toArray());
+ }
+ if (one instanceof Object[] && another instanceof Object[]) {
+ return ArrayUtil.isEquals(one, another);
+ } else {
+ throw new UnsupportedOperationException("The provided " + one + " and " + another + " are not Object[] or List!");
+ }
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ Class entityClass = ReflectionUtils.getClass(parameters.getProperty(DynamicParameterizedType.ENTITY));
+ String property = parameters.getProperty(DynamicParameterizedType.PROPERTY);
+ Type memberGenericType = ReflectionUtils.getMemberGenericTypeOrNull(entityClass, property);
+ if (memberGenericType instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) memberGenericType;
+ Type genericType = parameterizedType.getActualTypeArguments()[0];
+ if (genericType instanceof WildcardType) {
+ genericType = ((WildcardType) genericType).getUpperBounds()[0];
+ }
+ Class arrayElementClass = ReflectionUtils.getClass(genericType.getTypeName());
+ setArrayObjectClass(
+ arrayElementClass.isArray() ?
+ arrayElementClass :
+ ArrayUtil.toArrayClass(arrayElementClass)
+ );
+ sqlArrayType = parameters.getProperty(AbstractArrayType.SQL_ARRAY_TYPE);
+ if (sqlArrayType == null) {
+ if (Integer.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "integer";
+ } else if (Long.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "bigint";
+ } else if (Double.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "float8";
+ } else if (String.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "text";
+ } else if (UUID.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "uuid";
+ } else if (Date.class.isAssignableFrom(arrayElementClass) || LocalDateTime.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "timestamp";
+ } else if (Boolean.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "boolean";
+ } else if (BigDecimal.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "decimal";
+ } else if (LocalDate.class.isAssignableFrom(arrayElementClass)) {
+ sqlArrayType = "date";
+ } else {
+ throw new UnsupportedOperationException("The " + arrayElementClass + " is not supported yet!");
+ }
+ }
+
+ } else {
+ throw new UnsupportedOperationException("The property " + property + " in the " + entityClass + " entity is not parameterized!");
+ }
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LocalDateArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LocalDateArrayTypeDescriptor.java
new file mode 100644
index 000000000..44d897522
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LocalDateArrayTypeDescriptor.java
@@ -0,0 +1,17 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class LocalDateArrayTypeDescriptor
+ extends AbstractArrayTypeDescriptor {
+
+ public LocalDateArrayTypeDescriptor() {
+ super(java.time.LocalDate[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return "date";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LocalDateTimeArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LocalDateTimeArrayTypeDescriptor.java
new file mode 100644
index 000000000..c310f3f38
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LocalDateTimeArrayTypeDescriptor.java
@@ -0,0 +1,17 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class LocalDateTimeArrayTypeDescriptor
+ extends AbstractArrayTypeDescriptor {
+
+ public LocalDateTimeArrayTypeDescriptor() {
+ super(java.time.LocalDateTime[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return "timestamp";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LongArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LongArrayTypeDescriptor.java
new file mode 100644
index 000000000..7228dd212
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/LongArrayTypeDescriptor.java
@@ -0,0 +1,17 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class LongArrayTypeDescriptor
+ extends AbstractArrayTypeDescriptor {
+
+ public LongArrayTypeDescriptor() {
+ super(long[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return "bigint";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/StringArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/StringArrayTypeDescriptor.java
new file mode 100644
index 000000000..d4e61edf3
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/StringArrayTypeDescriptor.java
@@ -0,0 +1,18 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class StringArrayTypeDescriptor
+ extends AbstractArrayTypeDescriptor {
+
+ public StringArrayTypeDescriptor() {
+ super(String[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ String sqlArrayType = super.getSqlArrayType();
+ return sqlArrayType != null ? sqlArrayType : "text";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/TimestampArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/TimestampArrayTypeDescriptor.java
new file mode 100644
index 000000000..9ec1c9840
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/TimestampArrayTypeDescriptor.java
@@ -0,0 +1,18 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import java.util.Date;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class TimestampArrayTypeDescriptor extends AbstractArrayTypeDescriptor {
+
+ public TimestampArrayTypeDescriptor() {
+ super(Date[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return "timestamp";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/UUIDArrayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/UUIDArrayTypeDescriptor.java
new file mode 100644
index 000000000..5fab6e631
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/array/internal/UUIDArrayTypeDescriptor.java
@@ -0,0 +1,19 @@
+package com.vladmihalcea.hibernate.type.array.internal;
+
+import java.util.UUID;
+
+/**
+ * @author Rafael Acevedo
+ */
+public class UUIDArrayTypeDescriptor
+ extends AbstractArrayTypeDescriptor {
+
+ public UUIDArrayTypeDescriptor() {
+ super(UUID[].class);
+ }
+
+ @Override
+ protected String getSqlArrayType() {
+ return "uuid";
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/Inet.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/Inet.java
new file mode 100644
index 000000000..fa834f794
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/Inet.java
@@ -0,0 +1,59 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import org.hibernate.HibernateException;
+
+import java.io.Serializable;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/**
+ * The {@link Inet} object type is used to represent an IP address.
+ *
+ * For more details about how to use it,
+ * check out this article
+ * on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class Inet implements Serializable {
+
+ private final String address;
+
+ public Inet(String address) {
+ this.address = address;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ return Objects.equals(address, Inet.class.cast(o).address);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(address);
+ }
+
+ /**
+ * Get the associated {@link InetAddress} for the current {@link #address}.
+ *
+ * @return the associated {@link InetAddress}
+ */
+ public InetAddress toInetAddress() {
+ try {
+ String host = address.replaceAll("\\/.*$", "");
+ return Inet4Address.getByName(host);
+ } catch (UnknownHostException e) {
+ throw new HibernateException(
+ new IllegalArgumentException(e)
+ );
+ }
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/Iso8601MonthType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/Iso8601MonthType.java
new file mode 100644
index 000000000..fa6005922
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/Iso8601MonthType.java
@@ -0,0 +1,49 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.Iso8601MonthMonthTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.IntegerJdbcType;
+
+import java.time.Month;
+
+/**
+ * Maps a {@link Month} object type to a {@code INT} column type
+ * which is saved as value from 1 (January) to 12 (December),
+ * according to the ISO 8601 standard.
+ *
+ * @author Martin Panzer
+ */
+public class Iso8601MonthType extends AbstractHibernateType {
+
+ public static final Iso8601MonthType INSTANCE = new Iso8601MonthType();
+
+ public Iso8601MonthType() {
+ super(
+ IntegerJdbcType.INSTANCE,
+ Iso8601MonthMonthTypeDescriptor.INSTANCE
+ );
+ }
+
+ public Iso8601MonthType(Configuration configuration) {
+ super(
+ IntegerJdbcType.INSTANCE,
+ Iso8601MonthMonthTypeDescriptor.INSTANCE,
+ configuration
+ );
+ }
+
+ public Iso8601MonthType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public String getName() {
+ return "month";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/MonthDayDateType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/MonthDayDateType.java
new file mode 100644
index 000000000..664b5053f
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/MonthDayDateType.java
@@ -0,0 +1,42 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.MonthDayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.DateJdbcType;
+
+import java.time.MonthDay;
+
+/**
+ * Maps a Java {@link java.time.MonthDay} object to a {@code DATE} column type.
+ *
+ * @author Mladen Savic (mladensavic94@gmail.com)
+ */
+
+public class MonthDayDateType extends AbstractHibernateType {
+
+ public static final MonthDayDateType INSTANCE = new MonthDayDateType();
+
+
+ public MonthDayDateType() {
+ super(DateJdbcType.INSTANCE, MonthDayTypeDescriptor.INSTANCE);
+ }
+
+ public MonthDayDateType(Configuration configuration) {
+ super(DateJdbcType.INSTANCE, MonthDayTypeDescriptor.INSTANCE, configuration);
+ }
+
+ public MonthDayDateType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public String getName() {
+ return "monthday-date";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/MonthDayIntegerType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/MonthDayIntegerType.java
new file mode 100644
index 000000000..83b5e746d
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/MonthDayIntegerType.java
@@ -0,0 +1,41 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.MonthDayTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.IntegerJdbcType;
+
+import java.time.MonthDay;
+
+/**
+ * Maps a Java {@link java.time.MonthDay} object to a {@code INT} column type.
+ *
+ * @author Mladen Savic (mladensavic94@gmail.com)
+ */
+public class MonthDayIntegerType extends AbstractHibernateType {
+
+ public static final MonthDayIntegerType INSTANCE = new MonthDayIntegerType();
+
+
+ public MonthDayIntegerType() {
+ super(IntegerJdbcType.INSTANCE, MonthDayTypeDescriptor.INSTANCE);
+ }
+
+ public MonthDayIntegerType(Configuration configuration) {
+ super(IntegerJdbcType.INSTANCE, MonthDayTypeDescriptor.INSTANCE, configuration);
+ }
+
+ public MonthDayIntegerType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public String getName() {
+ return "monthday-int";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/NullableCharacterType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/NullableCharacterType.java
new file mode 100644
index 000000000..6ddbb3d26
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/NullableCharacterType.java
@@ -0,0 +1,52 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.ImmutableType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+/**
+ * Maps an {@link Character} to a nullable CHAR column type.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class NullableCharacterType extends ImmutableType {
+
+ public static final NullableCharacterType INSTANCE = new NullableCharacterType();
+
+ public NullableCharacterType() {
+ super(Character.class);
+ }
+
+ public NullableCharacterType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ super(Character.class, new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.CHAR;
+ }
+
+ @Override
+ public Character get(ResultSet rs, int position,
+ SharedSessionContractImplementor session, Object owner) throws SQLException {
+ String value = rs.getString(position);
+ return (value != null && value.length() > 0) ? value.charAt(0) : null;
+ }
+
+ @Override
+ public void set(PreparedStatement st, Character value, int index,
+ SharedSessionContractImplementor session) throws SQLException {
+ if (value == null) {
+ st.setNull(index, Types.CHAR);
+ } else {
+ st.setString(index, String.valueOf(value));
+ }
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLCITextType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLCITextType.java
new file mode 100644
index 000000000..698d44ca2
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLCITextType.java
@@ -0,0 +1,45 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.ImmutableType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+/**
+ * Maps a {@link String} object type to a PostgreSQL citext
+ * column type.
+ *
+ * @author Sergei Portnov
+ */
+public class PostgreSQLCITextType extends ImmutableType {
+
+ public static final PostgreSQLCITextType INSTANCE = new PostgreSQLCITextType();
+
+ public PostgreSQLCITextType() {
+ super(String.class);
+ }
+
+ public PostgreSQLCITextType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ super(String.class, new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.OTHER;
+ }
+
+ @Override
+ protected String get(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
+ Object value = rs.getObject(position);
+ return value == null ? null : value.toString();
+ }
+
+ @Override
+ protected void set(PreparedStatement st, String value, int index, SharedSessionContractImplementor session) throws SQLException {
+ st.setObject(index, value, Types.OTHER);
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLEnumType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLEnumType.java
new file mode 100644
index 000000000..a90af24be
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLEnumType.java
@@ -0,0 +1,94 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.util.ReflectionUtils;
+import org.hibernate.HibernateException;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.type.descriptor.java.JavaType;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Properties;
+
+/**
+ * Maps an {@link Enum} to a PostgreSQL ENUM column type.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class PostgreSQLEnumType extends org.hibernate.type.EnumType {
+
+ public static final PostgreSQLEnumType INSTANCE = new PostgreSQLEnumType();
+
+ private final Configuration configuration;
+
+ /**
+ * Initialization constructor taking the default {@link Configuration} object.
+ */
+ public PostgreSQLEnumType() {
+ this(Configuration.INSTANCE);
+ }
+
+ /**
+ * Initialization constructor taking a custom {@link Configuration} object.
+ *
+ * @param configuration custom {@link Configuration} object.
+ */
+ public PostgreSQLEnumType(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ public PostgreSQLEnumType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ /**
+ * Initialization constructor taking the {@link Class}.
+ *
+ * @param enumClass The enum type
+ */
+ public PostgreSQLEnumType(Class extends Enum> enumClass) {
+ this();
+
+ Class typeConfigurationClass = ReflectionUtils.getClassOrNull("org.hibernate.type.spi.TypeConfiguration");
+
+ if(typeConfigurationClass != null) {
+ Object typeConfiguration = ReflectionUtils.newInstance(typeConfigurationClass);
+
+ Class enumJavaTypeClass = ReflectionUtils.getClassOrNull("org.hibernate.type.descriptor.java.EnumJavaType");
+
+ Object enumJavaType = ReflectionUtils.newInstance(enumJavaTypeClass, new Object[] {enumClass}, new Class[]{enumClass.getClass()});
+
+ Object JavaTypeRegistry = ReflectionUtils.invokeGetter(typeConfiguration, "JavaTypeRegistry");
+
+ ReflectionUtils.invokeMethod(
+ JavaTypeRegistry,
+ ReflectionUtils.getMethod(JavaTypeRegistry, "addDescriptor", JavaType.class),
+ enumJavaType
+ );
+
+ ReflectionUtils.invokeSetter(this, "typeConfiguration", typeConfiguration);
+ }
+
+ Properties properties = new Properties();
+ properties.setProperty("enumClass", enumClass.getName());
+ properties.setProperty("useNamed", Boolean.TRUE.toString());
+ setParameterValues(properties);
+ }
+
+ public void nullSafeSet(
+ PreparedStatement st,
+ Object value,
+ int index,
+ SharedSessionContractImplementor session)
+ throws HibernateException, SQLException {
+ st.setObject(index, value != null ? ((Enum) value).name() : null, Types.OTHER);
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.VARCHAR;
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLHStoreType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLHStoreType.java
new file mode 100644
index 000000000..ae54cc81c
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLHStoreType.java
@@ -0,0 +1,52 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.ImmutableType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.postgresql.util.HStoreConverter;
+import org.postgresql.util.PGobject;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Map;
+
+/**
+ * Maps a {@link Map} object type to a PostgreSQL hstore
+ * column type.
+ *
+ * For more details about how to use it,
+ * check out this article
+ * on vladmihalcea.com .
+ *
+ * @author Edgar Asatryan
+ * @author Vlad Mihalcea
+ */
+public class PostgreSQLHStoreType extends ImmutableType {
+
+ public static final PostgreSQLHStoreType INSTANCE = new PostgreSQLHStoreType();
+
+ public PostgreSQLHStoreType() {
+ super(Map.class);
+ }
+
+ public PostgreSQLHStoreType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ super(Map.class, new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.OTHER;
+ }
+
+ @Override
+ protected Map get(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
+ return (Map) rs.getObject(position);
+ }
+
+ @Override
+ protected void set(PreparedStatement st, Map value, int index, SharedSessionContractImplementor session) throws SQLException {
+ st.setObject(index, value);
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLInetType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLInetType.java
new file mode 100644
index 000000000..cc4816704
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/PostgreSQLInetType.java
@@ -0,0 +1,56 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.ImmutableType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.postgresql.util.PGobject;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+/**
+ * Maps an {@link Inet} object type to a PostgreSQL INET column type.
+ *
+ * For more details about how to use it,
+ * check out this article
+ * on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class PostgreSQLInetType extends ImmutableType {
+
+ public static final PostgreSQLInetType INSTANCE = new PostgreSQLInetType();
+
+ public PostgreSQLInetType() {
+ super(Inet.class);
+ }
+
+ public PostgreSQLInetType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ super(Inet.class, new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.OTHER;
+ }
+
+ @Override
+ public Inet get(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
+ String ip = rs.getString(position);
+ return (ip != null) ? new Inet(ip) : null;
+ }
+
+ @Override
+ public void set(PreparedStatement st, Inet value, int index, SharedSessionContractImplementor session) throws SQLException {
+ if (value == null) {
+ st.setNull(index, Types.OTHER);
+ } else {
+ PGobject holder = new PGobject();
+ holder.setType("inet");
+ holder.setValue(value.getAddress());
+ st.setObject(index, holder);
+ }
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthDateType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthDateType.java
new file mode 100644
index 000000000..dbecd0da1
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthDateType.java
@@ -0,0 +1,49 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.YearMonthTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.DateJdbcType;
+
+import java.time.YearMonth;
+
+/**
+ * Maps a Java {@link java.time.YearMonth} object to a {@code DATE} column type.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class YearMonthDateType
+ extends AbstractHibernateType {
+
+ public static final YearMonthDateType INSTANCE = new YearMonthDateType();
+
+ public YearMonthDateType() {
+ super(
+ DateJdbcType.INSTANCE,
+ YearMonthTypeDescriptor.INSTANCE
+ );
+ }
+
+ public YearMonthDateType(Configuration configuration) {
+ super(
+ DateJdbcType.INSTANCE,
+ YearMonthTypeDescriptor.INSTANCE,
+ configuration
+ );
+ }
+
+ public YearMonthDateType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "yearmonth-date";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthEpochType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthEpochType.java
new file mode 100644
index 000000000..d820badf1
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthEpochType.java
@@ -0,0 +1,47 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.YearMonthEpochTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType;
+import java.time.YearMonth;
+
+/**
+ * Maps a Java {@link YearMonth} object to an small and continuous {@code INT} column type
+ * which defines the months that passed since the Unix epoch.
+ *
+ * @author Vlad Mihalcea
+ */
+public class YearMonthEpochType
+ extends AbstractHibernateType {
+
+ public static final YearMonthEpochType INSTANCE = new YearMonthEpochType();
+
+ public YearMonthEpochType() {
+ super(
+ SmallIntJdbcType.INSTANCE,
+ YearMonthEpochTypeDescriptor.INSTANCE
+ );
+ }
+
+ public YearMonthEpochType(Configuration configuration) {
+ super(
+ SmallIntJdbcType.INSTANCE,
+ YearMonthEpochTypeDescriptor.INSTANCE,
+ configuration
+ );
+ }
+
+ public YearMonthEpochType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "yearmonth-epoch";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthIntegerType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthIntegerType.java
new file mode 100644
index 000000000..471eb3f0b
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthIntegerType.java
@@ -0,0 +1,49 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.YearMonthTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.IntegerJdbcType;
+
+import java.time.YearMonth;
+
+/**
+ * Maps a Java {@link YearMonth} object to an {@code INT} column type.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class YearMonthIntegerType
+ extends AbstractHibernateType {
+
+ public static final YearMonthIntegerType INSTANCE = new YearMonthIntegerType();
+
+ public YearMonthIntegerType() {
+ super(
+ IntegerJdbcType.INSTANCE,
+ YearMonthTypeDescriptor.INSTANCE
+ );
+ }
+
+ public YearMonthIntegerType(Configuration configuration) {
+ super(
+ IntegerJdbcType.INSTANCE,
+ YearMonthTypeDescriptor.INSTANCE,
+ configuration
+ );
+ }
+
+ public YearMonthIntegerType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "yearmonth-int";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthTimestampType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthTimestampType.java
new file mode 100644
index 000000000..283642036
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearMonthTimestampType.java
@@ -0,0 +1,48 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.YearMonthTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
+
+import java.time.YearMonth;
+
+/**
+ * Maps a Java {@link YearMonth} object to a {@code TIMESTAMP} column type.
+ *
+ *
+ * @author Vlad Mihalcea
+ */
+public class YearMonthTimestampType
+ extends AbstractHibernateType {
+
+ public static final YearMonthTimestampType INSTANCE = new YearMonthTimestampType();
+
+ public YearMonthTimestampType() {
+ super(
+ TimestampJdbcType.INSTANCE,
+ YearMonthTypeDescriptor.INSTANCE
+ );
+ }
+
+ public YearMonthTimestampType(Configuration configuration) {
+ super(
+ TimestampJdbcType.INSTANCE,
+ YearMonthTypeDescriptor.INSTANCE,
+ configuration
+ );
+ }
+
+ public YearMonthTimestampType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "yearmonth-timestamp";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearType.java
new file mode 100644
index 000000000..dd2a48932
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/YearType.java
@@ -0,0 +1,49 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.YearTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType;
+
+import java.time.Year;
+
+/**
+ * Maps a Java {@link Year} object to an {@code INT} column type.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ */
+public class YearType
+ extends AbstractHibernateType {
+
+ public static final YearType INSTANCE = new YearType();
+
+ public YearType() {
+ super(
+ SmallIntJdbcType.INSTANCE,
+ YearTypeDescriptor.INSTANCE
+ );
+ }
+
+ public YearType(Configuration configuration) {
+ super(
+ SmallIntJdbcType.INSTANCE,
+ YearTypeDescriptor.INSTANCE,
+ configuration
+ );
+ }
+
+ public YearType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public String getName() {
+ return "year";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/ZoneIdType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/ZoneIdType.java
new file mode 100644
index 000000000..6d7af418b
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/ZoneIdType.java
@@ -0,0 +1,47 @@
+package com.vladmihalcea.hibernate.type.basic;
+
+import com.vladmihalcea.hibernate.type.AbstractHibernateType;
+import com.vladmihalcea.hibernate.type.basic.internal.ZoneIdTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.type.descriptor.jdbc.VarcharJdbcType;
+
+import java.time.ZoneId;
+
+/**
+ * Maps a Java {@link ZoneId} object to an {@code VARCHAR} column type.
+ *
+ * @author stonio
+ */
+public class ZoneIdType extends AbstractHibernateType {
+
+ public static final ZoneIdType INSTANCE = new ZoneIdType();
+
+ public ZoneIdType() {
+ super(
+ VarcharJdbcType.INSTANCE,
+ ZoneIdTypeDescriptor.INSTANCE
+ );
+ }
+
+ public ZoneIdType(Configuration configuration) {
+ super(
+ VarcharJdbcType.INSTANCE,
+ ZoneIdTypeDescriptor.INSTANCE,
+ configuration
+ );
+ }
+
+ public ZoneIdType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public String getName() {
+ return "zone-id";
+ }
+
+ @Override
+ protected boolean registerUnderJavaType() {
+ return true;
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/Iso8601MonthMonthTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/Iso8601MonthMonthTypeDescriptor.java
new file mode 100644
index 000000000..bfd99b226
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/Iso8601MonthMonthTypeDescriptor.java
@@ -0,0 +1,54 @@
+package com.vladmihalcea.hibernate.type.basic.internal;
+
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+
+import java.time.Month;
+import java.util.Objects;
+
+/**
+ * @author Martin Panzer
+ */
+public class Iso8601MonthMonthTypeDescriptor
+ extends AbstractClassJavaType {
+
+ public static final Iso8601MonthMonthTypeDescriptor INSTANCE = new Iso8601MonthMonthTypeDescriptor();
+
+ public Iso8601MonthMonthTypeDescriptor() {
+ super(Month.class);
+ }
+
+ @Override
+ public boolean areEqual(Month one, Month another) {
+ return Objects.equals(one, another);
+ }
+
+ @Override
+ public String toString(Month value) {
+ return value.toString();
+ }
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public X unwrap(Month value, Class type, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (Number.class.isAssignableFrom(type)) {
+ return (X) (Number) value.getValue();
+ }
+ throw unknownUnwrap(type);
+ }
+
+ @Override
+ public Month wrap(X value, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof Number) {
+ int numericValue = ((Number) (value)).intValue();
+ return Month.of(numericValue);
+ }
+ throw unknownWrap(value.getClass());
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/MonthDayTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/MonthDayTypeDescriptor.java
new file mode 100644
index 000000000..fe0e016bd
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/MonthDayTypeDescriptor.java
@@ -0,0 +1,78 @@
+package com.vladmihalcea.hibernate.type.basic.internal;
+
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.MonthDay;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * @author Mladen Savic (mladensavic94@gmail.com)
+ */
+
+public class MonthDayTypeDescriptor extends AbstractClassJavaType {
+
+ public static final MonthDayTypeDescriptor INSTANCE = new MonthDayTypeDescriptor();
+
+ protected MonthDayTypeDescriptor() {
+ super(MonthDay.class);
+ }
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public X unwrap(MonthDay monthDay, Class type, WrapperOptions wrapperOptions) {
+ if (monthDay == null) {
+ return null;
+ }
+ if (String.class.isAssignableFrom(type)) {
+ return (X) toString(monthDay);
+ }
+ if (Number.class.isAssignableFrom(type)) {
+ Integer numericValue = (monthDay.getMonthValue() * 100) + monthDay.getDayOfMonth();
+ return (X) (numericValue);
+ }
+ if (Date.class.isAssignableFrom(type)) {
+ int currentYear = LocalDate.now().getYear();
+ return (X) java.sql.Date.valueOf(monthDay.atYear(currentYear));
+ }
+
+ throw unknownUnwrap(type);
+ }
+
+ @Override
+ public MonthDay wrap(X value, WrapperOptions wrapperOptions) {
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof String) {
+ return fromString((String) value);
+ }
+ if (value instanceof Number) {
+ int numericValue = ((Number) (value)).intValue();
+ int month = numericValue / 100;
+ int dayOfMonth = numericValue % 100;
+ return MonthDay.of(month, dayOfMonth);
+ }
+ if (value instanceof Date) {
+ Date date = (Date) value;
+ return MonthDay.from(Instant.ofEpochMilli(date.getTime())
+ .atZone(ZoneId.systemDefault())
+ .toLocalDate());
+ }
+ throw unknownWrap(value.getClass());
+ }
+
+ @Override
+ public boolean areEqual(MonthDay one, MonthDay another) {
+ return Objects.equals(one, another);
+ }
+
+ @Override
+ public String toString(MonthDay value) {
+ return value.toString();
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearMonthEpochTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearMonthEpochTypeDescriptor.java
new file mode 100644
index 000000000..a288c666d
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearMonthEpochTypeDescriptor.java
@@ -0,0 +1,70 @@
+package com.vladmihalcea.hibernate.type.basic.internal;
+
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+
+import java.math.BigInteger;
+import java.time.LocalDate;
+import java.time.Period;
+import java.time.YearMonth;
+import java.util.Objects;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class YearMonthEpochTypeDescriptor
+ extends AbstractClassJavaType {
+
+ public static final YearMonth YEAR_MONTH_EPOCH = YearMonth.of(1970, 1);
+
+ public static final LocalDate LOCAL_DATE_EPOCH = YEAR_MONTH_EPOCH.atDay(1);
+
+ public static final YearMonthEpochTypeDescriptor INSTANCE = new YearMonthEpochTypeDescriptor();
+
+ public YearMonthEpochTypeDescriptor() {
+ super(YearMonth.class);
+ }
+
+ @Override
+ public boolean areEqual(YearMonth one, YearMonth another) {
+ return Objects.equals(one, another);
+ }
+
+ @Override
+ public String toString(YearMonth value) {
+ return value.toString();
+ }
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public X unwrap(YearMonth value, Class type, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ Number monthsSinceEpoch = Period.between(LOCAL_DATE_EPOCH, value.atDay(1)).toTotalMonths();
+ if (Short.class.isAssignableFrom(type)) {
+ return (X) (Short) (monthsSinceEpoch.shortValue());
+ }
+ if (Integer.class.isAssignableFrom(type)) {
+ return (X) (Integer) (monthsSinceEpoch.intValue());
+ }
+ if (Long.class.isAssignableFrom(type)) {
+ return (X) (Long) (monthsSinceEpoch.longValue());
+ }
+ if (BigInteger.class.isAssignableFrom(type)) {
+ return (X) (BigInteger.valueOf(monthsSinceEpoch.longValue()));
+ }
+ throw unknownUnwrap(type);
+ }
+
+ @Override
+ public YearMonth wrap(X value, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof Number) {
+ return YEAR_MONTH_EPOCH.plusMonths(((Number) value).intValue());
+ }
+ throw unknownWrap(value.getClass());
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearMonthTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearMonthTypeDescriptor.java
new file mode 100644
index 000000000..028b7e586
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearMonthTypeDescriptor.java
@@ -0,0 +1,83 @@
+package com.vladmihalcea.hibernate.type.basic.internal;
+
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class YearMonthTypeDescriptor
+ extends AbstractClassJavaType {
+
+ public static final YearMonthTypeDescriptor INSTANCE = new YearMonthTypeDescriptor();
+
+ public YearMonthTypeDescriptor() {
+ super(YearMonth.class);
+ }
+
+ @Override
+ public boolean areEqual(YearMonth one, YearMonth another) {
+ return Objects.equals(one, another);
+ }
+
+ @Override
+ public String toString(YearMonth value) {
+ return value.toString();
+ }
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public X unwrap(YearMonth value, Class type, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (String.class.isAssignableFrom(type)) {
+ return (X) toString(value);
+ }
+ if (Number.class.isAssignableFrom(type)) {
+ Integer numericValue = (value.getYear() * 100) + value.getMonth().getValue();
+ return (X) (numericValue);
+ }
+ if (Timestamp.class.isAssignableFrom(type)) {
+ return (X) java.sql.Timestamp.valueOf(value.atDay(1).atStartOfDay());
+ }
+ if (Date.class.isAssignableFrom(type)) {
+ return (X) java.sql.Date.valueOf(value.atDay(1));
+ }
+ throw unknownUnwrap(type);
+ }
+
+ @Override
+ public YearMonth wrap(X value, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof String) {
+ return fromString((String) value);
+ }
+ if (value instanceof Number) {
+ int numericValue = ((Number) (value)).intValue();
+ if(numericValue > 0) {
+ int year = numericValue / 100;
+ int month = numericValue % 100;
+ return YearMonth.of(year, month);
+ } else {
+ return null;
+ }
+ }
+ if (value instanceof Date) {
+ Date date = (Date) value;
+ return YearMonth.from(Instant.ofEpochMilli(date.getTime())
+ .atZone(ZoneId.systemDefault())
+ .toLocalDate());
+ }
+ throw unknownWrap(value.getClass());
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearTypeDescriptor.java
new file mode 100644
index 000000000..63bf2bb32
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/YearTypeDescriptor.java
@@ -0,0 +1,61 @@
+package com.vladmihalcea.hibernate.type.basic.internal;
+
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+
+import java.time.Year;
+import java.util.Objects;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class YearTypeDescriptor
+ extends AbstractClassJavaType {
+
+ public static final YearTypeDescriptor INSTANCE = new YearTypeDescriptor();
+
+ public YearTypeDescriptor() {
+ super(Year.class);
+ }
+
+ @Override
+ public boolean areEqual(Year one, Year another) {
+ return Objects.equals(one, another);
+ }
+
+ @Override
+ public String toString(Year value) {
+ return value.toString();
+ }
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public X unwrap(Year value, Class type, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (String.class.isAssignableFrom(type)) {
+ return (X) toString(value);
+ }
+ if (Number.class.isAssignableFrom(type)) {
+ Short numericValue = (short) value.getValue();
+ return (X) (numericValue);
+ }
+ throw unknownUnwrap(type);
+ }
+
+ @Override
+ public Year wrap(X value, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof String) {
+ return fromString((String) value);
+ }
+ if (value instanceof Number) {
+ short numericValue = ((Number) (value)).shortValue();
+ return Year.of(numericValue);
+ }
+ throw unknownWrap(value.getClass());
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/ZoneIdTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/ZoneIdTypeDescriptor.java
new file mode 100644
index 000000000..d3e5c4a55
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/basic/internal/ZoneIdTypeDescriptor.java
@@ -0,0 +1,63 @@
+package com.vladmihalcea.hibernate.type.basic.internal;
+
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+
+import java.time.ZoneId;
+import java.util.Comparator;
+
+/**
+ * Descriptor for {@link ZoneId} handling.
+ *
+ * @author stonio
+ */
+public class ZoneIdTypeDescriptor extends AbstractClassJavaType {
+
+ public static final ZoneIdTypeDescriptor INSTANCE = new ZoneIdTypeDescriptor();
+
+ public static class ZoneIdComparator implements Comparator {
+ public static final ZoneIdComparator INSTANCE = new ZoneIdComparator();
+
+ public int compare(ZoneId o1, ZoneId o2) {
+ return o1.getId().compareTo(o2.getId());
+ }
+ }
+
+ public ZoneIdTypeDescriptor() {
+ super(ZoneId.class);
+ }
+
+ public String toString(ZoneId value) {
+ return value.getId();
+ }
+
+ public ZoneId fromString(String string) {
+ return ZoneId.of(string);
+ }
+
+ @Override
+ public Comparator getComparator() {
+ return ZoneIdComparator.INSTANCE;
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ public X unwrap(ZoneId value, Class type, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (String.class.isAssignableFrom(type)) {
+ return (X) toString(value);
+ }
+ throw unknownUnwrap(type);
+ }
+
+ public ZoneId wrap(X value, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (String.class.isInstance(value)) {
+ return fromString((String) value);
+ }
+ throw unknownWrap(value.getClass());
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/OracleIntervalDayToSecondType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/OracleIntervalDayToSecondType.java
new file mode 100644
index 000000000..98c887d72
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/OracleIntervalDayToSecondType.java
@@ -0,0 +1,90 @@
+package com.vladmihalcea.hibernate.type.interval;
+
+import com.vladmihalcea.hibernate.type.ImmutableType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.HibernateException;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Maps a Java {@link Duration} object to a Oracle IntervalDS column type.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Vlad Mihalcea
+ * @since 2.6.1
+ */
+public class OracleIntervalDayToSecondType extends ImmutableType {
+
+ public static final OracleIntervalDayToSecondType INSTANCE = new OracleIntervalDayToSecondType();
+
+ public OracleIntervalDayToSecondType() {
+ super(Duration.class);
+ }
+
+ public OracleIntervalDayToSecondType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ super(Duration.class, new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ private static final int SQL_COLUMN_TYPE = -104;
+ private static final String INTERVAL_TOKENS = "%1$2d %2$2d:%3$2d:%4$2d.0";
+ private static final Pattern INTERVAL_PATTERN = Pattern.compile("(\\d+)\\s(\\d+):(\\d+):(\\d+)\\.\\d+");
+
+ @Override
+ protected Duration get(
+ ResultSet rs,
+ int position,
+ SharedSessionContractImplementor session,
+ Object owner) throws SQLException {
+ final String intervalValue = rs.getString(position);
+
+ if (intervalValue == null) {
+ return null;
+ }
+
+ Matcher matcher = INTERVAL_PATTERN.matcher(intervalValue);
+
+ if (matcher.matches()) {
+ Integer days = Integer.parseInt(matcher.group(1));
+ Integer hours = Integer.parseInt(matcher.group(2));
+ Integer minutes = Integer.parseInt(matcher.group(3));
+ Integer seconds = Integer.parseInt(matcher.group(4));
+
+ return Duration.ofDays(days)
+ .plus(hours, ChronoUnit.HOURS)
+ .plus(minutes, ChronoUnit.MINUTES)
+ .plus((long) Math.floor(seconds), ChronoUnit.SECONDS);
+ }
+
+ throw new HibernateException(
+ new IllegalArgumentException("The parsed interval " + intervalValue + " does not match the expected pattern: " + INTERVAL_PATTERN)
+ );
+ }
+
+ @Override
+ protected void set(PreparedStatement st, Duration value, int index, SharedSessionContractImplementor session) throws SQLException {
+ if (value == null) {
+ st.setNull(index, SQL_COLUMN_TYPE);
+ } else {
+ final int days = (int) value.toDays();
+ final int hours = (int) (value.toHours() % 24);
+ final int minutes = (int) (value.toMinutes() % 60);
+ final int seconds = (int) (value.getSeconds() % 60);
+
+ st.setString(index, String.format(INTERVAL_TOKENS, days, hours, minutes, seconds));
+ }
+ }
+
+ @Override
+ public int getSqlType() {
+ return SQL_COLUMN_TYPE;
+ }
+
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/PostgreSQLIntervalType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/PostgreSQLIntervalType.java
new file mode 100644
index 000000000..4f46d7cc9
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/PostgreSQLIntervalType.java
@@ -0,0 +1,79 @@
+package com.vladmihalcea.hibernate.type.interval;
+
+import com.vladmihalcea.hibernate.type.ImmutableType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.postgresql.util.PGInterval;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Maps a Java {@link Duration} object to a PostgreSQL Interval column type.
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ * @author Jan-Willem Gmelig Meyling
+ * @author Vlad Mihalcea
+ * @since 2.5.1
+ */
+public class PostgreSQLIntervalType extends ImmutableType {
+
+ private static final double MICROS_IN_SECOND = 1000000;
+
+ public static final PostgreSQLIntervalType INSTANCE = new PostgreSQLIntervalType();
+
+ public PostgreSQLIntervalType() {
+ super(Duration.class);
+ }
+
+ public PostgreSQLIntervalType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ super(Duration.class, new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ protected Duration get(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
+ final PGInterval interval = (PGInterval) rs.getObject(position);
+
+ if (interval == null) {
+ return null;
+ }
+
+ final int days = interval.getDays();
+ final int hours = interval.getHours();
+ final int minutes = interval.getMinutes();
+ final int seconds = (int) interval.getSeconds();
+ final int micros = (int) Math.round((interval.getSeconds() - seconds) * MICROS_IN_SECOND);
+
+ return Duration.ofDays(days)
+ .plus(hours, ChronoUnit.HOURS)
+ .plus(minutes, ChronoUnit.MINUTES)
+ .plus(seconds, ChronoUnit.SECONDS)
+ .plus(micros, ChronoUnit.MICROS);
+ }
+
+ @Override
+ protected void set(PreparedStatement st, Duration value, int index, SharedSessionContractImplementor session) throws SQLException {
+ if (value == null) {
+ st.setNull(index, Types.OTHER);
+ } else {
+ final int days = (int) value.toDays();
+ final int hours = (int) (value.toHours() % 24);
+ final int minutes = (int) (value.toMinutes() % 60);
+ final int seconds = (int) (value.getSeconds() % 60);
+ final int micros = value.getNano() / 1000;
+ final double secondsWithFraction = seconds + (micros / MICROS_IN_SECOND);
+ st.setObject(index, new PGInterval(0, 0, days, hours, minutes, secondsWithFraction));
+ }
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.OTHER;
+ }
+
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/PostgreSQLPeriodType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/PostgreSQLPeriodType.java
new file mode 100644
index 000000000..9bb9f4d14
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/interval/PostgreSQLPeriodType.java
@@ -0,0 +1,68 @@
+package com.vladmihalcea.hibernate.type.interval;
+
+import com.vladmihalcea.hibernate.type.ImmutableType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.postgresql.util.PGInterval;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.time.Duration;
+import java.time.Period;
+
+/**
+ * Maps a Java {@link Duration} object to a PostgreSQL Interval column type.
+ *
+ * @author Jan-Willem Gmelig Meyling
+ * @author Vlad Mihalcea
+ * @since 2.6.2
+ */
+public class PostgreSQLPeriodType extends ImmutableType {
+
+ public static final PostgreSQLPeriodType INSTANCE = new PostgreSQLPeriodType();
+
+ public PostgreSQLPeriodType() {
+ super(Period.class);
+ }
+
+ public PostgreSQLPeriodType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ super(Period.class, new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ protected Period get(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
+ final PGInterval interval = (PGInterval) rs.getObject(position);
+
+ if (interval == null) {
+ return null;
+ }
+
+ final int years = interval.getYears();
+ final int months = interval.getMonths();
+ final int days = interval.getDays();
+
+ return Period.ofYears(years)
+ .plusMonths(months)
+ .plusDays(days);
+ }
+
+ @Override
+ protected void set(PreparedStatement st, Period value, int index, SharedSessionContractImplementor session) throws SQLException {
+ if (value == null) {
+ st.setNull(index, Types.OTHER);
+ } else {
+ final int days = value.getDays();
+ final int months = value.getMonths();
+ final int years = value.getYears();
+ st.setObject(index, new PGInterval(years, months, days, 0, 0, 0));
+ }
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.OTHER;
+ }
+
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBinaryType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBinaryType.java
new file mode 100644
index 000000000..325885f55
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBinaryType.java
@@ -0,0 +1,95 @@
+package com.vladmihalcea.hibernate.type.json;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vladmihalcea.hibernate.type.DynamicMutableType;
+import com.vladmihalcea.hibernate.type.json.internal.JsonBinaryJdbcTypeDescriptor;
+import com.vladmihalcea.hibernate.type.json.internal.JsonJavaTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+
+import java.lang.reflect.Type;
+
+/**
+ *
+ * Maps any given Java object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setObject(int, Object)} at JDBC Driver level.
+ *
+ *
+ * If you are using PostgreSQL , you can use this {@link JsonBinaryType} to map both jsonb
and json
column types.
+ *
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ *
+ * If you want to use a more portable Hibernate Type
that can work on Oracle , SQL Server , PostgreSQL , MySQL , or H2 without any configuration changes, then you should use the {@link JsonType} instead.
+ *
+ *
+ * @author Vlad Mihalcea
+ */
+public class JsonBinaryType extends DynamicMutableType {
+
+ public static final JsonBinaryType INSTANCE = new JsonBinaryType();
+
+ public JsonBinaryType() {
+ super(
+ Object.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonBinaryType(Type javaType) {
+ super(
+ Object.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper(), javaType)
+ );
+ }
+
+ public JsonBinaryType(Configuration configuration) {
+ super(
+ Object.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(configuration.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonBinaryType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public JsonBinaryType(ObjectMapper objectMapper) {
+ super(
+ Object.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper))
+ );
+ }
+
+ public JsonBinaryType(ObjectMapperWrapper objectMapperWrapper) {
+ super(
+ Object.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(objectMapperWrapper)
+ );
+ }
+
+ public JsonBinaryType(ObjectMapper objectMapper, Type javaType) {
+ super(
+ Object.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper), javaType)
+ );
+ }
+
+ public JsonBinaryType(ObjectMapperWrapper objectMapperWrapper, Type javaType) {
+ super(
+ Object.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(objectMapperWrapper, javaType)
+ );
+ }
+
+ public String getName() {
+ return "jsonb";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBlobType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBlobType.java
new file mode 100644
index 000000000..c47ffaa71
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBlobType.java
@@ -0,0 +1,96 @@
+package com.vladmihalcea.hibernate.type.json;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vladmihalcea.hibernate.type.DynamicMutableType;
+import com.vladmihalcea.hibernate.type.json.internal.JsonJavaTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
+
+import java.lang.reflect.Type;
+import java.sql.Blob;
+
+/**
+ *
+ * Maps any given Java object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setBlob(int, Blob)} at JDBC Driver level.
+ *
+ *
+ * If you are using Oracle , you can use this {@link JsonBlobType} to map a {@code BLOB} column type storing JSON.
+ *
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ *
+ * If you want to use a more portable Hibernate Type
that can work on Oracle , SQL Server , PostgreSQL , MySQL , or H2 without any configuration changes, then you should use the {@link JsonType} instead.
+ *
+ *
+ * @author Vlad Mihalcea
+ */
+public class JsonBlobType extends DynamicMutableType {
+
+ public static final JsonBlobType INSTANCE = new JsonBlobType();
+
+ public JsonBlobType() {
+ super(
+ Object.class,
+ org.hibernate.type.descriptor.jdbc.BlobJdbcType.DEFAULT,
+ new JsonJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonBlobType(Type javaType) {
+ super(
+ Object.class,
+ org.hibernate.type.descriptor.jdbc.BlobJdbcType.DEFAULT,
+ new JsonJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper(), javaType)
+ );
+ }
+
+ public JsonBlobType(Configuration configuration) {
+ super(
+ Object.class,
+ org.hibernate.type.descriptor.jdbc.BlobJdbcType.DEFAULT,
+ new JsonJavaTypeDescriptor(configuration.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonBlobType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public JsonBlobType(ObjectMapper objectMapper) {
+ super(
+ Object.class,
+ org.hibernate.type.descriptor.jdbc.BlobJdbcType.DEFAULT,
+ new JsonJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper))
+ );
+ }
+
+ public JsonBlobType(ObjectMapperWrapper objectMapperWrapper) {
+ super(
+ Object.class,
+ org.hibernate.type.descriptor.jdbc.BlobJdbcType.DEFAULT,
+ new JsonJavaTypeDescriptor(objectMapperWrapper)
+ );
+ }
+
+ public JsonBlobType(ObjectMapper objectMapper, Type javaType) {
+ super(
+ Object.class,
+ org.hibernate.type.descriptor.jdbc.BlobJdbcType.DEFAULT,
+ new JsonJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper), javaType)
+ );
+ }
+
+ public JsonBlobType(ObjectMapperWrapper objectMapperWrapper, Type javaType) {
+ super(
+ Object.class,
+ org.hibernate.type.descriptor.jdbc.BlobJdbcType.DEFAULT,
+ new JsonJavaTypeDescriptor(objectMapperWrapper, javaType)
+ );
+ }
+
+ public String getName() {
+ return "jsonb-lob";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeBinaryType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeBinaryType.java
new file mode 100644
index 000000000..939126958
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeBinaryType.java
@@ -0,0 +1,70 @@
+package com.vladmihalcea.hibernate.type.json;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vladmihalcea.hibernate.type.DynamicMutableType;
+import com.vladmihalcea.hibernate.type.json.internal.JsonBinaryJdbcTypeDescriptor;
+import com.vladmihalcea.hibernate.type.json.internal.JsonNodeJavaTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+
+/**
+ *
+ * Maps a Jackson {@link JsonNode} object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setObject(int, Object)} at JDBC Driver level.
+ *
+ *
+ * For instance, if you are using PostgreSQL , you can use the {@link JsonNodeBinaryType} to map both {@code jsonb} and {@code json} column types to a Jackson {@link JsonNode} object.
+ *
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ *
+ * If you want to use a more portable Hibernate Type
that can work on Oracle , SQL Server , PostgreSQL , MySQL , or H2 without any configuration changes, then you should use the {@link JsonType} instead.
+ *
+ *
+ * @author Vlad Mihalcea
+ */
+public class JsonNodeBinaryType extends DynamicMutableType {
+
+ public static final JsonNodeBinaryType INSTANCE = new JsonNodeBinaryType();
+
+ public JsonNodeBinaryType() {
+ super(
+ JsonNode.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonNodeJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonNodeBinaryType(Configuration configuration) {
+ super(
+ JsonNode.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonNodeJavaTypeDescriptor(configuration.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonNodeBinaryType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public JsonNodeBinaryType(ObjectMapper objectMapper) {
+ super(
+ JsonNode.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonNodeJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper))
+ );
+ }
+
+ public JsonNodeBinaryType(ObjectMapperWrapper objectMapperWrapper) {
+ super(
+ JsonNode.class,
+ JsonBinaryJdbcTypeDescriptor.INSTANCE,
+ new JsonNodeJavaTypeDescriptor(objectMapperWrapper)
+ );
+ }
+
+ public String getName() {
+ return "jsonb-node";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeStringType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeStringType.java
new file mode 100644
index 000000000..aea933eaa
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeStringType.java
@@ -0,0 +1,70 @@
+package com.vladmihalcea.hibernate.type.json;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vladmihalcea.hibernate.type.DynamicMutableType;
+import com.vladmihalcea.hibernate.type.json.internal.JsonNodeJavaTypeDescriptor;
+import com.vladmihalcea.hibernate.type.json.internal.JsonStringJdbcTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+
+/**
+ *
+ * Maps a Jackson {@link JsonNode} object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setString(int, String)} at JDBC Driver level.
+ *
+ *
+ * For instance, if you are using MySQL , you can use the {@link JsonNodeStringType} to map the {@code json} column type to a Jackson {@link JsonNode} object.
+ *
+ *
+ * For more details about how to use it, check out this article on vladmihalcea.com .
+ *
+ *
+ * If you want to use a more portable Hibernate Type
that can work on Oracle , SQL Server , PostgreSQL , MySQL , or H2 without any configuration changes, then you should use the {@link JsonType} instead.
+ *
+ *
+ * @author Vlad Mihalcea
+ */
+public class JsonNodeStringType extends DynamicMutableType {
+
+ public static final JsonNodeStringType INSTANCE = new JsonNodeStringType();
+
+ public JsonNodeStringType() {
+ super(
+ JsonNode.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonNodeJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonNodeStringType(Configuration configuration) {
+ super(
+ JsonNode.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonNodeJavaTypeDescriptor(configuration.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonNodeStringType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public JsonNodeStringType(ObjectMapper objectMapper) {
+ super(
+ JsonNode.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonNodeJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper))
+ );
+ }
+
+ public JsonNodeStringType(ObjectMapperWrapper objectMapperWrapper) {
+ super(
+ JsonNode.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonNodeJavaTypeDescriptor(objectMapperWrapper)
+ );
+ }
+
+ public String getName() {
+ return "jsonb-node";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonStringType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonStringType.java
new file mode 100644
index 000000000..130f748ba
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonStringType.java
@@ -0,0 +1,102 @@
+package com.vladmihalcea.hibernate.type.json;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vladmihalcea.hibernate.type.DynamicMutableType;
+import com.vladmihalcea.hibernate.type.json.internal.JsonJavaTypeDescriptor;
+import com.vladmihalcea.hibernate.type.json.internal.JsonStringJdbcTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+
+import java.lang.reflect.Type;
+
+/**
+ *
+ * Maps any given Java object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setString(int, String)} at JDBC Driver level.
+ *
+ *
+ * If you are using Oracle , you can use this {@link JsonStringType} to map a {@code VARCHAR2} column type storing JSON. For more details, check out this article on vladmihalcea.com .
+ *
+ *
+ * If you are using SQL Server , you can use this {@link JsonStringType} to map an {@code NVARCHAR} column type storing JSON. For more details, check out this article on vladmihalcea.com .
+ *
+ *
+ * If you are using MySQL , you can use this {@link JsonStringType} to map the {@code json} column type. For more details, check out this article on vladmihalcea.com .
+ *
+ *
+ * If you are using PostgreSQL , then you should NOT use this {@link JsonStringType}. You should use {@link JsonBinaryType} instead. For more details, check out this article on vladmihalcea.com .
+ *
+ *
+ *
+ * If you want to use a more portable Hibernate Type
that can work on Oracle , SQL Server , PostgreSQL , MySQL , or H2 without any configuration changes, then you should use the {@link JsonType} instead.
+ *
+ *
+ * @author Vlad Mihalcea
+ */
+public class JsonStringType extends DynamicMutableType {
+
+ public static final JsonStringType INSTANCE = new JsonStringType();
+
+ public JsonStringType() {
+ super(
+ Object.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonStringType(Type javaType) {
+ super(
+ Object.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper(), javaType)
+ );
+ }
+
+ public JsonStringType(Configuration configuration) {
+ super(
+ Object.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(configuration.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonStringType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public JsonStringType(ObjectMapper objectMapper) {
+ super(
+ Object.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper))
+ );
+ }
+
+ public JsonStringType(ObjectMapperWrapper objectMapperWrapper) {
+ super(
+ Object.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(objectMapperWrapper)
+ );
+ }
+
+ public JsonStringType(ObjectMapper objectMapper, Type javaType) {
+ super(
+ Object.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper), javaType)
+ );
+ }
+
+ public JsonStringType(ObjectMapperWrapper objectMapperWrapper, Type javaType) {
+ super(
+ Object.class,
+ JsonStringJdbcTypeDescriptor.INSTANCE,
+ new JsonJavaTypeDescriptor(objectMapperWrapper, javaType)
+ );
+ }
+
+ public String getName() {
+ return "json";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonType.java
new file mode 100644
index 000000000..7453c7ea7
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/JsonType.java
@@ -0,0 +1,100 @@
+package com.vladmihalcea.hibernate.type.json;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vladmihalcea.hibernate.type.DynamicMutableType;
+import com.vladmihalcea.hibernate.type.json.internal.JsonJavaTypeDescriptor;
+import com.vladmihalcea.hibernate.type.json.internal.JsonJdbcTypeDescriptor;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+
+import java.lang.reflect.Type;
+
+/**
+ *
+ * {@link JsonType} allows you to map any given JSON object (e.g., POJO, Map<String, Object>
, List<T>, JsonNode
) on any of the following database systems:
+ *
+ *
+ * PostgreSQL - for both jsonb
and json
column types
+ * MySQL - for the json
column type
+ * SQL Server - for the NVARCHAR
column type storing JSON
+ * Oracle - for the VARCHAR
column type storing JSON
+ * H2 - for the json
column type
+ *
+ *
+ *
+ * For more details about how to use the {@link JsonType}, check out this article on vladmihalcea.com .
+ *
+ *
+ * If you are using Oracle and want to store JSON objects in a BLOB
column types, then you should use the {@link JsonBlobType} instead. For more details, check out this article on vladmihalcea.com .
+ *
+ *
+ * @author Vlad Mihalcea
+ */
+public class JsonType extends DynamicMutableType {
+
+ public static final JsonType INSTANCE = new JsonType();
+
+ public JsonType() {
+ super(
+ Object.class,
+ new JsonJdbcTypeDescriptor(),
+ new JsonJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonType(Type javaType) {
+ super(
+ Object.class,
+ new JsonJdbcTypeDescriptor(),
+ new JsonJavaTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper(), javaType)
+ );
+ }
+
+ public JsonType(Configuration configuration) {
+ super(
+ Object.class,
+ new JsonJdbcTypeDescriptor(),
+ new JsonJavaTypeDescriptor(configuration.getObjectMapperWrapper())
+ );
+ }
+
+ public JsonType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ this(new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ public JsonType(ObjectMapper objectMapper) {
+ super(
+ Object.class,
+ new JsonJdbcTypeDescriptor(),
+ new JsonJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper))
+ );
+ }
+
+ public JsonType(ObjectMapperWrapper objectMapperWrapper) {
+ super(
+ Object.class,
+ new JsonJdbcTypeDescriptor(),
+ new JsonJavaTypeDescriptor(objectMapperWrapper)
+ );
+ }
+
+ public JsonType(ObjectMapper objectMapper, Type javaType) {
+ super(
+ Object.class,
+ new JsonJdbcTypeDescriptor(),
+ new JsonJavaTypeDescriptor(new ObjectMapperWrapper(objectMapper), javaType)
+ );
+ }
+
+ public JsonType(ObjectMapperWrapper objectMapperWrapper, Type javaType) {
+ super(
+ Object.class,
+ new JsonJdbcTypeDescriptor(),
+ new JsonJavaTypeDescriptor(objectMapperWrapper, javaType)
+ );
+ }
+
+ public String getName() {
+ return "json";
+ }
+}
\ No newline at end of file
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/AbstractJsonJdbcTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/AbstractJsonJdbcTypeDescriptor.java
new file mode 100644
index 000000000..80e54e2f9
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/AbstractJsonJdbcTypeDescriptor.java
@@ -0,0 +1,55 @@
+package com.vladmihalcea.hibernate.type.json.internal;
+
+import org.hibernate.type.descriptor.ValueExtractor;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.BasicExtractor;
+import org.hibernate.type.descriptor.jdbc.JdbcType;
+
+import java.sql.CallableStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public abstract class AbstractJsonJdbcTypeDescriptor implements JdbcType {
+
+ @Override
+ public int getJdbcTypeCode() {
+ return Types.OTHER;
+ }
+
+ @Override
+ public ValueExtractor getExtractor(final JavaType javaType) {
+ return new BasicExtractor(javaType, this) {
+ @Override
+ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
+ return javaType.wrap(extractJson(rs, paramIndex), options);
+ }
+
+ @Override
+ protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
+ return javaType.wrap(extractJson(statement, index), options);
+ }
+
+ @Override
+ protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
+ return javaType.wrap(extractJson(statement, name), options);
+ }
+ };
+ }
+
+ protected Object extractJson(ResultSet rs, int paramIndex) throws SQLException {
+ return rs.getObject(paramIndex);
+ }
+
+ protected Object extractJson(CallableStatement statement, int index) throws SQLException {
+ return statement.getObject(index);
+ }
+
+ protected Object extractJson(CallableStatement statement, String name) throws SQLException {
+ return statement.getObject(name);
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JacksonUtil.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JacksonUtil.java
new file mode 100644
index 000000000..05770cc49
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JacksonUtil.java
@@ -0,0 +1,32 @@
+package com.vladmihalcea.hibernate.type.json.internal;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+
+import java.lang.reflect.Type;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class JacksonUtil {
+
+ public static T fromString(String string, Class clazz) {
+ return ObjectMapperWrapper.INSTANCE.fromString(string, clazz);
+ }
+
+ public static T fromString(String string, Type type) {
+ return ObjectMapperWrapper.INSTANCE.fromString(string, type);
+ }
+
+ public static String toString(Object value) {
+ return ObjectMapperWrapper.INSTANCE.toString(value);
+ }
+
+ public static JsonNode toJsonNode(String value) {
+ return ObjectMapperWrapper.INSTANCE.toJsonNode(value);
+ }
+
+ public static T clone(T value) {
+ return ObjectMapperWrapper.INSTANCE.clone(value);
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonBinaryJdbcTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonBinaryJdbcTypeDescriptor.java
new file mode 100644
index 000000000..dfda74f73
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonBinaryJdbcTypeDescriptor.java
@@ -0,0 +1,35 @@
+package com.vladmihalcea.hibernate.type.json.internal;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hibernate.type.descriptor.ValueBinder;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.BasicBinder;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class JsonBinaryJdbcTypeDescriptor extends AbstractJsonJdbcTypeDescriptor {
+
+ public static final JsonBinaryJdbcTypeDescriptor INSTANCE = new JsonBinaryJdbcTypeDescriptor();
+
+ @Override
+ public ValueBinder getBinder(final JavaType javaType) {
+ return new BasicBinder(javaType, this) {
+ @Override
+ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
+ st.setObject(index, javaType.unwrap(value, JsonNode.class, options), getJdbcTypeCode());
+ }
+
+ @Override
+ protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
+ throws SQLException {
+ st.setObject(name, javaType.unwrap(value, JsonNode.class, options), getJdbcTypeCode());
+ }
+ };
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonBytesJdbcTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonBytesJdbcTypeDescriptor.java
new file mode 100644
index 000000000..51eecc9a6
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonBytesJdbcTypeDescriptor.java
@@ -0,0 +1,74 @@
+package com.vladmihalcea.hibernate.type.json.internal;
+
+import org.hibernate.type.descriptor.ValueBinder;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.BasicBinder;
+
+import java.io.UnsupportedEncodingException;
+import java.sql.*;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class JsonBytesJdbcTypeDescriptor extends AbstractJsonJdbcTypeDescriptor {
+
+ public static final JsonBytesJdbcTypeDescriptor INSTANCE = new JsonBytesJdbcTypeDescriptor();
+
+ public static final String CHARSET = "UTF8";
+
+ @Override
+ public int getJdbcTypeCode() {
+ return Types.BINARY;
+ }
+
+ @Override
+ public ValueBinder getBinder(final JavaType JavaType) {
+ return new BasicBinder(JavaType, this) {
+ @Override
+ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
+ st.setBytes(index, toJsonBytes(JavaType.unwrap(value, String.class, options)));
+ }
+
+ @Override
+ protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
+ throws SQLException {
+ st.setBytes(name, toJsonBytes(JavaType.unwrap(value, String.class, options)));
+ }
+ };
+ }
+
+ @Override
+ protected Object extractJson(ResultSet rs, int paramIndex) throws SQLException {
+ return fromJsonBytes(rs.getBytes(paramIndex));
+ }
+
+ @Override
+ protected Object extractJson(CallableStatement statement, int index) throws SQLException {
+ return fromJsonBytes(statement.getBytes(index));
+ }
+
+ @Override
+ protected Object extractJson(CallableStatement statement, String name) throws SQLException {
+ return fromJsonBytes(statement.getBytes(name));
+ }
+
+ protected byte[] toJsonBytes(String jsonValue) {
+ try {
+ return jsonValue.getBytes(CHARSET);
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ protected String fromJsonBytes(byte[] jsonBytes) {
+ if (jsonBytes == null) {
+ return null;
+ }
+ try {
+ return new String(jsonBytes, CHARSET);
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonJavaTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonJavaTypeDescriptor.java
new file mode 100644
index 000000000..4c7a08369
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonJavaTypeDescriptor.java
@@ -0,0 +1,204 @@
+package com.vladmihalcea.hibernate.type.json.internal;
+
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+import com.vladmihalcea.hibernate.util.LogUtils;
+import com.vladmihalcea.hibernate.util.ReflectionUtils;
+import org.hibernate.HibernateException;
+import org.hibernate.annotations.common.reflection.XProperty;
+import org.hibernate.annotations.common.reflection.java.JavaXMember;
+import org.hibernate.engine.jdbc.BinaryStream;
+import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+import org.hibernate.type.descriptor.java.BlobJavaType;
+import org.hibernate.type.descriptor.java.DataHelper;
+import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
+import org.hibernate.usertype.DynamicParameterizedType;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.sql.Blob;
+import java.sql.SQLException;
+import java.util.*;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class JsonJavaTypeDescriptor
+ extends AbstractClassJavaType implements DynamicParameterizedType {
+
+ private Type propertyType;
+
+ private Class propertyClass;
+
+ private ObjectMapperWrapper objectMapperWrapper;
+
+ public JsonJavaTypeDescriptor() {
+ this(ObjectMapperWrapper.INSTANCE);
+ }
+
+ public JsonJavaTypeDescriptor(Type type) {
+ this();
+ setPropertyClass(type);
+ }
+
+ public JsonJavaTypeDescriptor(final ObjectMapperWrapper objectMapperWrapper) {
+ super(Object.class, new MutableMutabilityPlan() {
+ @Override
+ protected Object deepCopyNotNull(Object value) {
+ return objectMapperWrapper.clone(value);
+ }
+ });
+ this.objectMapperWrapper = objectMapperWrapper;
+ }
+
+ public JsonJavaTypeDescriptor(final ObjectMapperWrapper objectMapperWrapper, Type type) {
+ this(objectMapperWrapper);
+ setPropertyClass(type);
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ final XProperty xProperty = (XProperty) parameters.get(DynamicParameterizedType.XPROPERTY);
+ Type type = (xProperty instanceof JavaXMember) ?
+ ReflectionUtils.invokeGetter(xProperty, "javaType") :
+ ((ParameterType) parameters.get(PARAMETER_TYPE)).getReturnedClass();
+ setPropertyClass(type);
+ }
+
+ @Override
+ public boolean areEqual(Object one, Object another) {
+ if (one == another) {
+ return true;
+ }
+ if (one == null || another == null) {
+ return false;
+ }
+ if (one instanceof String && another instanceof String) {
+ return one.equals(another);
+ }
+ if (one instanceof Collection && another instanceof Collection) {
+ return Objects.equals(one, another);
+ }
+ if (one.getClass().equals(another.getClass()) &&
+ ReflectionUtils.getDeclaredMethodOrNull(one.getClass(), "equals", Object.class) != null) {
+ return one.equals(another);
+ }
+ return objectMapperWrapper.toJsonNode(objectMapperWrapper.toString(one)).equals(
+ objectMapperWrapper.toJsonNode(objectMapperWrapper.toString(another))
+ );
+ }
+
+ @Override
+ public String toString(Object value) {
+ return objectMapperWrapper.toString(value);
+ }
+
+ @Override
+ public Object fromString(CharSequence string) {
+ if (String.class.isAssignableFrom(propertyClass)) {
+ return string;
+ }
+ return objectMapperWrapper.fromString((String) string, propertyType);
+ }
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public X unwrap(Object value, Class type, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+
+ if (String.class.isAssignableFrom(type)) {
+ return value instanceof String ? (X) value : (X) toString(value);
+ } else if (BinaryStream.class.isAssignableFrom(type) ||
+ byte[].class.isAssignableFrom(type)) {
+ String stringValue = (value instanceof String) ? (String) value : toString(value);
+
+ return (X) new BinaryStreamImpl(DataHelper.extractBytes(new ByteArrayInputStream(stringValue.getBytes())));
+ } else if (Blob.class.isAssignableFrom(type)) {
+ String stringValue = (value instanceof String) ? (String) value : toString(value);
+
+ final Blob blob = BlobJavaType.INSTANCE.fromString(stringValue);
+ return (X) blob;
+ } else if (Object.class.isAssignableFrom(type)) {
+ String stringValue = (value instanceof String) ? (String) value : toString(value);
+ return (X) objectMapperWrapper.toJsonNode(stringValue);
+ }
+
+ throw unknownUnwrap(type);
+ }
+
+ @Override
+ public Object wrap(X value, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+
+ Blob blob = null;
+
+ if (Blob.class.isAssignableFrom(value.getClass())) {
+ blob = options.getLobCreator().wrap((Blob) value);
+ } else if (byte[].class.isAssignableFrom(value.getClass())) {
+ blob = options.getLobCreator().createBlob((byte[]) value);
+ } else if (InputStream.class.isAssignableFrom(value.getClass())) {
+ InputStream inputStream = (InputStream) value;
+ try {
+ blob = options.getLobCreator().createBlob(inputStream, inputStream.available());
+ } catch (IOException e) {
+ throw unknownWrap(value.getClass());
+ }
+ }
+
+ String stringValue;
+ try {
+ stringValue = (blob != null) ? new String(DataHelper.extractBytes(blob.getBinaryStream())) : value.toString();
+ } catch (SQLException e) {
+ throw new HibernateException("Unable to extract binary stream from Blob", e);
+ }
+
+ return fromString(stringValue);
+ }
+
+ private void setPropertyClass(Type type) {
+ this.propertyType = type;
+ if (type instanceof ParameterizedType) {
+ type = ((ParameterizedType) type).getRawType();
+ } else if (type instanceof TypeVariable) {
+ type = ((TypeVariable) type).getGenericDeclaration().getClass();
+ }
+ this.propertyClass = (Class) type;
+ validatePropertyType();
+ }
+
+ private void validatePropertyType() {
+ if(Collection.class.isAssignableFrom(propertyClass)) {
+ if (propertyType instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) propertyType;
+
+ for(Class genericType : ReflectionUtils.getGenericTypes(parameterizedType)) {
+ if(validatedTypes.contains(genericType)) {
+ continue;
+ }
+ validatedTypes.add(genericType);
+ Method equalsMethod = ReflectionUtils.getMethodOrNull(genericType, "equals", Object.class);
+ Method hashCodeMethod = ReflectionUtils.getMethodOrNull(genericType, "hashCode");
+
+ if(equalsMethod == null ||
+ hashCodeMethod == null ||
+ Object.class.equals(equalsMethod.getDeclaringClass()) ||
+ Object.class.equals(hashCodeMethod.getDeclaringClass())) {
+ LogUtils.LOGGER.warn("The {} class should override both the equals and hashCode methods based on the JSON object value it represents!", genericType);
+ }
+ }
+ }
+ }
+ }
+
+ private static List validatedTypes = new ArrayList<>();
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonJdbcTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonJdbcTypeDescriptor.java
new file mode 100644
index 000000000..cb9e4ec5e
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonJdbcTypeDescriptor.java
@@ -0,0 +1,84 @@
+package com.vladmihalcea.hibernate.type.json.internal;
+
+import org.hibernate.dialect.Dialect;
+import org.hibernate.dialect.H2Dialect;
+import org.hibernate.dialect.PostgreSQLDialect;
+import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver;
+import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter;
+import org.hibernate.type.descriptor.ValueBinder;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.BasicBinder;
+
+import java.sql.*;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class JsonJdbcTypeDescriptor extends AbstractJsonJdbcTypeDescriptor {
+
+ private volatile Dialect dialect;
+ private volatile AbstractJsonJdbcTypeDescriptor sqlTypeDescriptor;
+
+ @Override
+ public ValueBinder getBinder(final JavaType javaType) {
+ return new BasicBinder(javaType, this) {
+ @Override
+ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
+ sqlTypeDescriptor(st.getConnection()).getBinder(javaType).bind(
+ st, value, index, options
+ );
+ }
+
+ @Override
+ protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
+ throws SQLException {
+ sqlTypeDescriptor(st.getConnection()).getBinder(javaType).bind(
+ st, value, name, options
+ );
+ }
+ };
+ }
+
+ @Override
+ protected Object extractJson(ResultSet rs, int paramIndex) throws SQLException {
+ return sqlTypeDescriptor(rs.getStatement().getConnection()).extractJson(rs, paramIndex);
+ }
+
+ @Override
+ protected Object extractJson(CallableStatement statement, int index) throws SQLException {
+ return sqlTypeDescriptor(statement.getConnection()).extractJson(statement, index);
+ }
+
+ @Override
+ protected Object extractJson(CallableStatement statement, String name) throws SQLException {
+ return sqlTypeDescriptor(statement.getConnection()).extractJson(statement, name);
+ }
+
+ private AbstractJsonJdbcTypeDescriptor sqlTypeDescriptor(Connection connection) {
+ if(sqlTypeDescriptor == null) {
+ sqlTypeDescriptor = resolveSqlTypeDescriptor(connection);
+ }
+ return sqlTypeDescriptor;
+ }
+
+ private AbstractJsonJdbcTypeDescriptor resolveSqlTypeDescriptor(Connection connection) {
+ try {
+ StandardDialectResolver dialectResolver = new StandardDialectResolver();
+ dialect = dialectResolver.resolveDialect(
+ new DatabaseMetaDataDialectResolutionInfoAdapter(connection.getMetaData())
+ );
+ if(PostgreSQLDialect.class.isInstance(dialect)) {
+ return JsonBinaryJdbcTypeDescriptor.INSTANCE;
+ } else if(H2Dialect.class.isInstance(dialect)) {
+ return JsonBytesJdbcTypeDescriptor.INSTANCE;
+ } else {
+ return JsonStringJdbcTypeDescriptor.INSTANCE;
+ }
+ } catch (SQLException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonNodeJavaTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonNodeJavaTypeDescriptor.java
new file mode 100644
index 000000000..5925d27d6
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonNodeJavaTypeDescriptor.java
@@ -0,0 +1,91 @@
+package com.vladmihalcea.hibernate.type.json.internal;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
+import org.hibernate.SharedSessionContract;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
+
+import java.io.Serializable;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class JsonNodeJavaTypeDescriptor
+ extends AbstractClassJavaType {
+
+ public static final JsonNodeJavaTypeDescriptor INSTANCE = new JsonNodeJavaTypeDescriptor();
+
+ private ObjectMapperWrapper objectMapperWrapper;
+
+ public JsonNodeJavaTypeDescriptor() {
+ this(ObjectMapperWrapper.INSTANCE);
+ }
+
+ public JsonNodeJavaTypeDescriptor(final ObjectMapperWrapper objectMapperWrapper) {
+ super(JsonNode.class, new MutableMutabilityPlan() {
+ @Override
+ public Serializable disassemble(JsonNode value, SharedSessionContract session) {
+ return JacksonUtil.toString(value);
+ }
+
+ @Override
+ public JsonNode assemble(Serializable cached, SharedSessionContract session) {
+ return JacksonUtil.toJsonNode((String) cached);
+ }
+
+ @Override
+ protected JsonNode deepCopyNotNull(JsonNode value) {
+ return objectMapperWrapper.clone(value);
+ }
+ });
+ this.objectMapperWrapper = objectMapperWrapper;
+ }
+
+ @Override
+ public boolean areEqual(JsonNode one, JsonNode another) {
+ if (one == another) {
+ return true;
+ }
+ if (one == null || another == null) {
+ return false;
+ }
+ return objectMapperWrapper.toJsonNode(objectMapperWrapper.toString(one)).equals(
+ objectMapperWrapper.toJsonNode(objectMapperWrapper.toString(another)));
+ }
+
+ @Override
+ public String toString(JsonNode value) {
+ return objectMapperWrapper.toString(value);
+ }
+
+ @Override
+ public JsonNode fromString(CharSequence string) {
+ return objectMapperWrapper.toJsonNode((String) string);
+ }
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public X unwrap(JsonNode value, Class type, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ if (String.class.isAssignableFrom(type)) {
+ return (X) toString(value);
+ }
+ if (JsonNode.class.isAssignableFrom(type)) {
+ return (X) objectMapperWrapper.toJsonNode(toString(value));
+ }
+ throw unknownUnwrap(type);
+ }
+
+ @Override
+ public JsonNode wrap(X value, WrapperOptions options) {
+ if (value == null) {
+ return null;
+ }
+ return fromString(value.toString());
+ }
+
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonStringJdbcTypeDescriptor.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonStringJdbcTypeDescriptor.java
new file mode 100644
index 000000000..00d0ddbde
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonStringJdbcTypeDescriptor.java
@@ -0,0 +1,52 @@
+package com.vladmihalcea.hibernate.type.json.internal;
+
+import org.hibernate.type.descriptor.ValueBinder;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.JavaType;
+import org.hibernate.type.descriptor.jdbc.BasicBinder;
+
+import java.sql.*;
+
+/**
+ * @author Vlad Mihalcea
+ */
+public class JsonStringJdbcTypeDescriptor extends AbstractJsonJdbcTypeDescriptor {
+
+ public static final JsonStringJdbcTypeDescriptor INSTANCE = new JsonStringJdbcTypeDescriptor();
+
+ @Override
+ public int getJdbcTypeCode() {
+ return Types.VARCHAR;
+ }
+
+ @Override
+ public ValueBinder getBinder(final JavaType javaType) {
+ return new BasicBinder(javaType, this) {
+ @Override
+ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
+ st.setString(index, javaType.unwrap(value, String.class, options));
+ }
+
+ @Override
+ protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
+ throws SQLException {
+ st.setString(name, javaType.unwrap(value, String.class, options));
+ }
+ };
+ }
+
+ @Override
+ protected Object extractJson(ResultSet rs, int paramIndex) throws SQLException {
+ return rs.getString(paramIndex);
+ }
+
+ @Override
+ protected Object extractJson(CallableStatement statement, int index) throws SQLException {
+ return statement.getString(index);
+ }
+
+ @Override
+ protected Object extractJson(CallableStatement statement, String name) throws SQLException {
+ return statement.getString(name);
+ }
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/PostgreSQLRangeType.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/PostgreSQLRangeType.java
new file mode 100644
index 000000000..714590891
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/PostgreSQLRangeType.java
@@ -0,0 +1,147 @@
+package com.vladmihalcea.hibernate.type.range;
+
+import com.vladmihalcea.hibernate.type.ImmutableType;
+import com.vladmihalcea.hibernate.type.util.Configuration;
+import com.vladmihalcea.hibernate.util.ReflectionUtils;
+import org.hibernate.HibernateException;
+import org.hibernate.annotations.common.reflection.XProperty;
+import org.hibernate.annotations.common.reflection.java.JavaXMember;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.usertype.DynamicParameterizedType;
+import org.postgresql.util.PGobject;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.util.Properties;
+
+/**
+ * Maps a {@link Range} object type to a PostgreSQL range
+ * column type.
+ *
+ * Supported range types:
+ *
+ * int4range
+ * int8range
+ * numrange
+ * tsrange
+ * tstzrange
+ * daterange
+ *
+ *
+ * For more details about how to use it,
+ * check out this article
+ * on vladmihalcea.com .
+ *
+ * @author Edgar Asatryan
+ * @author Vlad Mihalcea
+ */
+public class PostgreSQLRangeType extends ImmutableType implements DynamicParameterizedType {
+
+ public static final PostgreSQLRangeType INSTANCE = new PostgreSQLRangeType();
+
+ private Type type;
+
+ public PostgreSQLRangeType() {
+ super(Range.class);
+ }
+
+ public PostgreSQLRangeType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapContext) {
+ super(Range.class, new Configuration(typeBootstrapContext.getConfigurationSettings()));
+ }
+
+ @Override
+ public int getSqlType() {
+ return Types.OTHER;
+ }
+
+ @Override
+ protected Range get(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner) throws SQLException {
+ PGobject pgObject = (PGobject) rs.getObject(position);
+
+ if (pgObject == null) {
+ return null;
+ }
+
+ String type = pgObject.getType();
+ String value = pgObject.getValue();
+
+ switch (type) {
+ case "int4range":
+ return Range.integerRange(value);
+ case "int8range":
+ return Range.longRange(value);
+ case "numrange":
+ return Range.bigDecimalRange(value);
+ case "tsrange":
+ return Range.localDateTimeRange(value);
+ case "tstzrange":
+ return Range.zonedDateTimeRange(value);
+ case "daterange":
+ return Range.localDateRange(value);
+ default:
+ throw new HibernateException(
+ new IllegalStateException("The range type [" + type + "] is not supported!")
+ );
+ }
+ }
+
+ @Override
+ protected void set(PreparedStatement st, Range range, int index, SharedSessionContractImplementor session) throws SQLException {
+
+ if (range == null) {
+ st.setNull(index, Types.OTHER);
+ } else {
+ PGobject object = new PGobject();
+ object.setType(determineRangeType(range));
+ object.setValue(range.asString());
+
+ st.setObject(index, object);
+ }
+ }
+
+ private static String determineRangeType(Range> range) {
+ Class> clazz = range.getClazz();
+
+ if (clazz.equals(Integer.class)) {
+ return "int4range";
+ } else if (clazz.equals(Long.class)) {
+ return "int8range";
+ } else if (clazz.equals(BigDecimal.class)) {
+ return "numrange";
+ } else if (clazz.equals(LocalDateTime.class)) {
+ return "tsrange";
+ } else if (clazz.equals(ZonedDateTime.class)) {
+ return "tstzrange";
+ } else if (clazz.equals(LocalDate.class)) {
+ return "daterange";
+ }
+
+ throw new HibernateException(
+ new IllegalStateException("The class [" + clazz.getName() + "] is not supported!")
+ );
+ }
+
+ @Override
+ public void setParameterValues(Properties parameters) {
+ final XProperty xProperty = (XProperty) parameters.get(DynamicParameterizedType.XPROPERTY);
+ if (xProperty instanceof JavaXMember) {
+ type = ReflectionUtils.invokeGetter(xProperty, "javaType");
+ } else {
+ type = ((ParameterType) parameters.get(PARAMETER_TYPE)).getReturnedClass();
+ }
+ }
+
+ public Class> getElementType() {
+ return type instanceof ParameterizedType ?
+ (Class>) ((ParameterizedType) type).getActualTypeArguments()[0] : null;
+ }
+
+}
diff --git a/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java
new file mode 100644
index 000000000..aff8356e2
--- /dev/null
+++ b/hibernate-types-60/src/main/java/com/vladmihalcea/hibernate/type/range/Range.java
@@ -0,0 +1,639 @@
+package com.vladmihalcea.hibernate.type.range;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Represents the range/interval with two bounds. Abstraction follows the semantics of the mathematical interval. The
+ * range can be unbounded or open from the left or/and unbounded from the right. The range supports half-open or closed
+ * bounds on both sides.
+ *
+ *
+ * The class has some very simple methods for usability. For example {@link Range#contains(Comparable)} method can tell user whether
+ * this range contains argument or not. The {@link Range#contains(Range)} helps to find out whether this range fully
+ * enclosing argument or not.
+ *
+ * For more details about how to use it,
+ * check out this article
+ * on vladmihalcea.com .
+ *
+ * @author Edgar Asatryan
+ * @author Vlad Mihalcea
+ */
+public final class Range implements Serializable {
+
+ public static final int LOWER_INCLUSIVE = 1 << 1;
+ public static final int LOWER_EXCLUSIVE = 1 << 2;
+ public static final int UPPER_INCLUSIVE = 1 << 3;
+ public static final int UPPER_EXCLUSIVE = 1 << 4;
+ public static final int LOWER_INFINITE = (1 << 5) | LOWER_EXCLUSIVE;
+ public static final int UPPER_INFINITE = (1 << 6) | UPPER_EXCLUSIVE;
+
+ public static final String EMPTY = "empty";
+
+ public static final String INFINITY = "infinity";
+
+ private static final DateTimeFormatter LOCAL_DATE_TIME = new DateTimeFormatterBuilder()
+ .appendPattern("yyyy-MM-dd HH:mm:ss")
+ .optionalStart()
+ .appendPattern(".")
+ .appendFraction(ChronoField.NANO_OF_SECOND, 1, 6, false)
+ .optionalEnd()
+ .toFormatter();
+
+ private static final DateTimeFormatter ZONE_DATE_TIME = new DateTimeFormatterBuilder()
+ .appendPattern("yyyy-MM-dd HH:mm:ss")
+ .optionalStart()
+ .appendPattern(".")
+ .appendFraction(ChronoField.NANO_OF_SECOND, 1, 6, false)
+ .optionalEnd()
+ .appendOffset("+HH:mm", "Z")
+ .toFormatter();
+
+ private final T lower;
+ private final T upper;
+ private final int mask;
+ private final Class clazz;
+
+ private Range(T lower, T upper, int mask, Class clazz) {
+ this.lower = lower;
+ this.upper = upper;
+ this.mask = mask;
+ this.clazz = clazz;
+
+ if (isBounded() && lower.compareTo(upper) > 0) {
+ throw new IllegalArgumentException("The lower bound is greater then upper!");
+ }
+ }
+
+ /**
+ * Creates the closed range with provided bounds.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * [a, b] = {x | a <= x <= b}
+ * } .
+ *
+ * @param lower The lower bound, never null.
+ * @param upper The upper bound, never null.
+ * @param The type of bounds.
+ *
+ * @return The closed range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range closed(T lower, T upper) {
+ Objects.requireNonNull(lower);
+ Objects.requireNonNull(upper);
+ return new Range<>(lower, upper, LOWER_INCLUSIVE | UPPER_INCLUSIVE, (Class) lower.getClass());
+ }
+
+ /**
+ * Creates the open range with provided bounds.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * (a, b) = {x | a < x < b}
+ * }
+ *
+ * @param lower The lower bound, never null.
+ * @param upper The upper bound, never null.
+ * @param The type of bounds.
+ *
+ * @return The range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range open(T lower, T upper) {
+ Objects.requireNonNull(lower);
+ Objects.requireNonNull(upper);
+ return new Range<>(lower, upper, LOWER_EXCLUSIVE | UPPER_EXCLUSIVE, (Class) lower.getClass());
+ }
+
+ /**
+ * Creates the left-open, right-closed range with provided bounds.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * (a, b] = {x | a < x <= b}
+ * }
+ *
+ * @param lower The lower bound, never null.
+ * @param upper The upper bound, never null.
+ * @param The type of bounds.
+ *
+ * @return The range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range openClosed(T lower, T upper) {
+ Objects.requireNonNull(lower);
+ Objects.requireNonNull(upper);
+ return new Range<>(lower, upper, LOWER_EXCLUSIVE | UPPER_INCLUSIVE, (Class) lower.getClass());
+ }
+
+ /**
+ * Creates the left-closed, right-open range with provided bounds.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * [a, b) = {x | a <= x < b}
+ * }
+ *
+ * @param lower The lower bound, never null.
+ * @param upper The upper bound, never null.
+ * @param The type of bounds.
+ *
+ * @return The range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range closedOpen(T lower, T upper) {
+ Objects.requireNonNull(lower);
+ Objects.requireNonNull(upper);
+ return new Range<>(lower, upper, LOWER_INCLUSIVE | UPPER_EXCLUSIVE, (Class) lower.getClass());
+ }
+
+ /**
+ * Creates the left-bounded, left-open and right-unbounded range with provided lower bound.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * (a, +∞) = {x | x > a}
+ * }
+ *
+ * @param lower The lower bound, never null.
+ * @param The type of bounds.
+ *
+ * @return The range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range openInfinite(T lower) {
+ Objects.requireNonNull(lower);
+ return new Range<>(lower, null, LOWER_EXCLUSIVE | UPPER_INFINITE, (Class) lower.getClass());
+ }
+
+ /**
+ * Creates the left-bounded, left-closed and right-unbounded range with provided lower bound.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * [a, +∞) = {x | x >= a}
+ * }
+ *
+ * @param lower The lower bound, never null.
+ * @param The type of bounds.
+ *
+ * @return The range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range closedInfinite(T lower) {
+ Objects.requireNonNull(lower);
+ return new Range(lower, null, LOWER_INCLUSIVE | UPPER_INFINITE, lower.getClass());
+ }
+
+ /**
+ * Creates the left-unbounded, right-bounded and right-open range with provided upper bound.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * (-∞, b) = {x | x < b}
+ * }
+ *
+ * @param upper The upper bound, never null.
+ * @param The type of bounds.
+ *
+ * @return The range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range infiniteOpen(T upper) {
+ Objects.requireNonNull(upper);
+ return new Range<>(null, upper, UPPER_EXCLUSIVE | LOWER_INFINITE, (Class) upper.getClass());
+ }
+
+ /**
+ * Creates the left-unbounded, right-bounded and right-closed range with provided upper bound.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * (-∞, b] = {x | x =< b}
+ * }
+ *
+ * @param upper The upper bound, never null.
+ * @param The type of bounds.
+ *
+ * @return The range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range infiniteClosed(T upper) {
+ Objects.requireNonNull(upper);
+ return new Range<>(null, upper, UPPER_INCLUSIVE | LOWER_INFINITE, (Class) upper.getClass());
+ }
+
+ /**
+ * Creates the unbounded at both ends range with provided upper bound.
+ *
+ * The mathematical equivalent will be:
+ *
{@code
+ * (-∞, +∞) = ℝ
+ * }
+ *
+ * @param cls The range class, never null.
+ * @param The type of bounds.
+ *
+ * @return The infinite range.
+ */
+ @SuppressWarnings("unchecked")
+ public static > Range infinite(Class cls) {
+ return new Range<>(null, null, LOWER_INFINITE | UPPER_INFINITE, cls);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Range ofString(String str, Function converter, Class clazz) {
+ if(str.equals(EMPTY)) {
+ return emptyRange(clazz);
+ }
+
+ int mask = str.charAt(0) == '[' ? LOWER_INCLUSIVE : LOWER_EXCLUSIVE;
+ mask |= str.charAt(str.length() - 1) == ']' ? UPPER_INCLUSIVE : UPPER_EXCLUSIVE;
+
+ int delim = str.indexOf(',');
+
+ if (delim == -1) {
+ throw new IllegalArgumentException("Cannot find comma character");
+ }
+
+ String lowerStr = str.substring(1, delim);
+ String upperStr = str.substring(delim + 1, str.length() - 1);
+
+ if (lowerStr.length() == 0 || lowerStr.endsWith(INFINITY)) {
+ mask |= LOWER_INFINITE;
+ }
+
+ if (upperStr.length() == 0 || upperStr.endsWith(INFINITY)) {
+ mask |= UPPER_INFINITE;
+ }
+
+ T lower = null;
+ T upper = null;
+
+ if ((mask & LOWER_INFINITE) != LOWER_INFINITE) {
+ lower = converter.apply(lowerStr);
+ }
+
+ if ((mask & UPPER_INFINITE) != UPPER_INFINITE) {
+ upper = converter.apply(upperStr);
+ }
+
+ return new Range<>(lower, upper, mask, clazz);
+ }
+
+ /**
+ * Creates the {@code BigDecimal} range from provided string:
+ * {@code
+ * Range closed = Range.bigDecimalRange("[0.1,1.1]");
+ * Range halfOpen = Range.bigDecimalRange("(0.1,1.1]");
+ * Range