Skip to content

Commit

Permalink
Add support for Oracle 21c JSON columns #422
Browse files Browse the repository at this point in the history
Add support for customizing the JsonType underlying Oracle column type #424
  • Loading branch information
vladmihalcea committed Apr 11, 2022
1 parent a2f92c0 commit 9d72b1a
Show file tree
Hide file tree
Showing 24 changed files with 1,559 additions and 75 deletions.
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.vladmihalcea.hibernate.type.AbstractHibernateType;
import com.vladmihalcea.hibernate.type.json.internal.JsonBlobSqlTypeDescriptor;
import com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor;
import com.vladmihalcea.hibernate.type.util.Configuration;
import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
Expand Down Expand Up @@ -33,21 +34,21 @@ public class JsonBlobType extends AbstractHibernateType<Object> implements Dynam

public JsonBlobType() {
super(
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
JsonBlobSqlTypeDescriptor.INSTANCE,
new JsonTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper())
);
}

public JsonBlobType(Type javaType) {
super(
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
JsonBlobSqlTypeDescriptor.INSTANCE,
new JsonTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper(), javaType)
);
}

public JsonBlobType(Configuration configuration) {
super(
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
JsonBlobSqlTypeDescriptor.INSTANCE,
new JsonTypeDescriptor(configuration.getObjectMapperWrapper()),
configuration
);
Expand All @@ -59,28 +60,28 @@ public JsonBlobType(org.hibernate.type.spi.TypeBootstrapContext typeBootstrapCon

public JsonBlobType(ObjectMapper objectMapper) {
super(
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
JsonBlobSqlTypeDescriptor.INSTANCE,
new JsonTypeDescriptor(new ObjectMapperWrapper(objectMapper))
);
}

public JsonBlobType(ObjectMapperWrapper objectMapperWrapper) {
super(
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
JsonBlobSqlTypeDescriptor.INSTANCE,
new JsonTypeDescriptor(objectMapperWrapper)
);
}

public JsonBlobType(ObjectMapper objectMapper, Type javaType) {
super(
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
JsonBlobSqlTypeDescriptor.INSTANCE,
new JsonTypeDescriptor(new ObjectMapperWrapper(objectMapper), javaType)
);
}

public JsonBlobType(ObjectMapperWrapper objectMapperWrapper, Type javaType) {
super(
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
JsonBlobSqlTypeDescriptor.INSTANCE,
new JsonTypeDescriptor(objectMapperWrapper, javaType)
);
}
Expand Down
Expand Up @@ -6,7 +6,9 @@
import com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor;
import com.vladmihalcea.hibernate.type.util.Configuration;
import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.usertype.DynamicParameterizedType;
import org.hibernate.usertype.ParameterizedType;

import java.lang.reflect.Type;
import java.util.Properties;
Expand All @@ -16,20 +18,47 @@
* {@link JsonType} allows you to map any given JSON object (e.g., POJO, <code>Map&lt;String, Object&gt;</code>, List&lt;T&gt;, <code>JsonNode</code>) on any of the following database systems:
* </p>
* <ul>
* <li><strong>PostgreSQL</strong> - for both <code>jsonb</code> and <code>json</code> column types</li>
* <li><strong>MySQL</strong> - for the <code>json</code> column type</li>
* <li><strong>SQL Server</strong> - for the <code>NVARCHAR</code> column type storing JSON</li>
* <li><strong>Oracle</strong> - for the <code>VARCHAR</code> column type storing JSON</li>
* <li><strong>H2</strong> - for the <code>json</code> column type</li>
* <li><strong>PostgreSQL</strong> - for both <strong><code>jsonb</code></strong> and <strong><code>json</code></strong> column types</li>
* <li><strong>MySQL</strong> - for the <strong><code>json</code></strong> column type</li>
* <li><strong>SQL Server</strong> - for the <strong><code>NVARCHAR</code></strong> column type storing JSON</li>
* <li><strong>Oracle</strong> - for the <strong><code>JSON</code></strong> column type if you're using Oracle 21c or the <strong><code>VARCHAR</code></strong> column type storing JSON if you're using an older Oracle version</li>
* <li><strong>H2</strong> - for the <strong><code>json</code></strong> column type</li>
* </ul>
*
* <p>
* If you switch to Oracle 21c from an older version, then you should also migrate your {@code JSON} columns to the native JSON type since this binary type performs better than
* {@code VARCHAR2} or {@code BLOB} column types.
* </p>
* <p>
* However, if you don't want to migrate to the new {@code JSON} data type, then you will have to change your {@link JsonType} mappings to use the {@code varchar} type on
* Oracle 21c or newer versions, like in the following example:
* </p>
* <pre>
* {@code @Type(}
* type = "com.vladmihalcea.hibernate.type.json.JsonType",
* parameters = {@code @Parameter(}
* name = "hibernate.types.default.json.type",
* value = "varchar"
* )
* )
* </pre>
* <p>
* For more details about how to use the {@link JsonType}, check out <a href="https://vladmihalcea.com/how-to-map-json-objects-using-generic-hibernate-types/">this article</a> on <a href="https://vladmihalcea.com/">vladmihalcea.com</a>.
* </p>
* <p>
* If you are using <strong>Oracle</strong> and want to store JSON objects in a <code>BLOB</code> column types, then you should use the {@link JsonBlobType} instead. For more details, check out <a href="https://vladmihalcea.com/oracle-json-jpa-hibernate/">this article</a> on <a href="https://vladmihalcea.com/">vladmihalcea.com</a>.
* If you are using <strong>Oracle</strong> and want to store JSON objects in a <code>BLOB</code> column type, then you can use the {@link JsonBlobType} instead. For more details, check out <a href="https://vladmihalcea.com/oracle-json-jpa-hibernate/">this article</a> on <a href="https://vladmihalcea.com/">vladmihalcea.com</a>.
* </p>
*
* <p>
* Or, you can customize the {@link JsonType} as follows:
* </p>
* <pre>
* {@code @Type(}
* type = "com.vladmihalcea.hibernate.type.json.JsonType",
* parameters = {@code @Parameter(}
* name = "hibernate.types.default.json.type",
* value = "blob"
* )
* )
* </pre>
* @author Vlad Mihalcea
*/
public class JsonType
Expand All @@ -53,7 +82,7 @@ public JsonType(Type javaType) {

public JsonType(Configuration configuration) {
super(
new JsonSqlTypeDescriptor(),
new JsonSqlTypeDescriptor(configuration.getProperties()),
new JsonTypeDescriptor(configuration.getObjectMapperWrapper()),
configuration
);
Expand Down Expand Up @@ -98,6 +127,10 @@ public String getName() {
@Override
public void setParameterValues(Properties parameters) {
((JsonTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters);
SqlTypeDescriptor sqlTypeDescriptor = getSqlTypeDescriptor();
if(sqlTypeDescriptor instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) sqlTypeDescriptor;
parameterizedType.setParameterValues(parameters);
}
}

}
@@ -0,0 +1,31 @@
package com.vladmihalcea.hibernate.type.json.internal;

import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.BlobTypeDescriptor;

/**
* @author Vlad Mihalcea
*/
public class JsonBlobSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor {

public static final JsonBlobSqlTypeDescriptor INSTANCE = new JsonBlobSqlTypeDescriptor();

private BlobTypeDescriptor blobTypeDescriptor = BlobTypeDescriptor.DEFAULT;

@Override
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
return blobTypeDescriptor.getBinder(javaTypeDescriptor);
}

@Override
public int getSqlType() {
return blobTypeDescriptor.getSqlType();
}

@Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
return blobTypeDescriptor.getExtractor(javaTypeDescriptor);
}
}
@@ -1,13 +1,15 @@
package com.vladmihalcea.hibernate.type.json.internal;

import org.hibernate.dialect.Database;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.BasicBinder;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;

/**
* @author Vlad Mihalcea
Expand All @@ -16,11 +18,32 @@ public class JsonBytesSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor {

public static final JsonBytesSqlTypeDescriptor INSTANCE = new JsonBytesSqlTypeDescriptor();

private static final Map<Database, JsonBytesSqlTypeDescriptor> INSTANCE_MAP = new HashMap<>();

static {
INSTANCE_MAP.put(Database.H2, INSTANCE);
INSTANCE_MAP.put(Database.ORACLE, new JsonBytesSqlTypeDescriptor(2016));
}

public static JsonBytesSqlTypeDescriptor of(Database database) {
return INSTANCE_MAP.get(database);
}

public static final String CHARSET = "UTF8";

private final int jdbcType;

public JsonBytesSqlTypeDescriptor() {
this.jdbcType = Types.BINARY;
}

public JsonBytesSqlTypeDescriptor(int jdbcType) {
this.jdbcType = jdbcType;
}

@Override
public int getSqlType() {
return Types.BINARY;
return jdbcType;
}

@Override
Expand Down
@@ -1,28 +1,35 @@
package com.vladmihalcea.hibernate.type.json.internal;

import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.PostgreSQL81Dialect;
import com.vladmihalcea.hibernate.type.util.Configuration;
import org.hibernate.dialect.*;
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.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.BasicBinder;
import org.hibernate.type.descriptor.sql.BasicExtractor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.usertype.ParameterizedType;

import java.sql.*;
import java.util.Properties;

/**
* @author Vlad Mihalcea
*/
public class JsonSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor {
public class JsonSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor implements ParameterizedType {

private volatile Dialect dialect;
private volatile AbstractJsonSqlTypeDescriptor sqlTypeDescriptor;

private volatile Properties properties;

public JsonSqlTypeDescriptor() {
}

public JsonSqlTypeDescriptor(Properties properties) {
this.properties = properties;
}

@Override
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>(javaTypeDescriptor, this) {
Expand Down Expand Up @@ -68,20 +75,53 @@ private AbstractJsonSqlTypeDescriptor sqlTypeDescriptor(Connection connection) {
private AbstractJsonSqlTypeDescriptor resolveSqlTypeDescriptor(Connection connection) {
try {
StandardDialectResolver dialectResolver = new StandardDialectResolver();
dialect = dialectResolver.resolveDialect(
new DatabaseMetaDataDialectResolutionInfoAdapter(connection.getMetaData())
);
if(PostgreSQL81Dialect.class.isInstance(dialect)) {
DatabaseMetaDataDialectResolutionInfoAdapter metaDataInfo = new DatabaseMetaDataDialectResolutionInfoAdapter(connection.getMetaData());
dialect = dialectResolver.resolveDialect(metaDataInfo);
if(dialect instanceof PostgreSQL81Dialect) {
return JsonBinarySqlTypeDescriptor.INSTANCE;
} else if(H2Dialect.class.isInstance(dialect)) {
} else if(dialect instanceof H2Dialect) {
return JsonBytesSqlTypeDescriptor.INSTANCE;
} else {
return JsonStringSqlTypeDescriptor.INSTANCE;
} else if(dialect instanceof Oracle12cDialect) {
if(properties != null) {
String defaultJsonType = properties.getProperty(Configuration.PropertyKey.DEFAULT_JSON_TYPE.getKey());
if (defaultJsonType != null) {
switch (defaultJsonType) {
case "json":
return JsonBytesSqlTypeDescriptor.of(Database.ORACLE);
case "blob":
case "clob":
return JsonBlobSqlTypeDescriptor.INSTANCE;
case "varchar":
case "varchar2":
case "string":
case "text":
return JsonStringSqlTypeDescriptor.INSTANCE;
}
}
}
if(metaDataInfo.getDatabaseMajorVersion() >= 21) {
return JsonBytesSqlTypeDescriptor.of(Database.ORACLE);
}
}
return JsonStringSqlTypeDescriptor.INSTANCE;
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}

@Override
public int getSqlType() {
return sqlTypeDescriptor != null ?
sqlTypeDescriptor.getSqlType() :
super.getSqlType();
}

@Override
public void setParameterValues(Properties parameters) {
if(properties == null) {
properties = parameters;
} else {
properties.putAll(parameters);
}
}
}
Expand Up @@ -43,7 +43,8 @@ public class Configuration {
public enum PropertyKey {
JACKSON_OBJECT_MAPPER("hibernate.types.jackson.object.mapper"),
JSON_SERIALIZER("hibernate.types.json.serializer"),
PRINT_BANNER("hibernate.types.print.banner");
PRINT_BANNER("hibernate.types.print.banner"),
DEFAULT_JSON_TYPE("hibernate.types.default.json.type");

private final String key;

Expand Down

0 comments on commit 9d72b1a

Please sign in to comment.