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 12, 2022
1 parent a2f92c0 commit d20c417
Show file tree
Hide file tree
Showing 66 changed files with 4,621 additions and 188 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,50 +34,50 @@ 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
);
}

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,8 +6,11 @@
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 javax.persistence.Column;
import java.lang.reflect.Type;
import java.util.Properties;

Expand All @@ -16,24 +19,44 @@
* {@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 just have to provide the column type via the JPA {@link Column#columnDefinition()} attribute,
* like in the following example:
* </p>
* <pre>
* {@code @Type(}type = "com.vladmihalcea.hibernate.type.json.JsonType")
* {@code @Column(}columnDefinition = "VARCHAR2")
* </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 use the {@link JsonType}, but you'll have to specify the underlying column type
* using the JPA {@link Column#columnDefinition()} attribute, like this:
* </p>
* <pre>
* {@code @Type(}type = "com.vladmihalcea.hibernate.type.json.JsonType")
* {@code @Column(}columnDefinition = "BLOB")
* </pre>
*
* @author Vlad Mihalcea
*/
public class JsonType
extends AbstractHibernateType<Object> implements DynamicParameterizedType {
extends AbstractHibernateType<Object> implements DynamicParameterizedType {

public static final JsonType INSTANCE = new JsonType();

Expand All @@ -53,7 +76,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 @@ -94,6 +117,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,12 +1,17 @@
package com.vladmihalcea.hibernate.type.json.internal;

import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.Oracle8iDialect;
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.sql.*;
import java.util.HashMap;
import java.util.Map;

/**
* @author Vlad Mihalcea
Expand All @@ -15,8 +20,34 @@ public class JsonBytesSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor {

public static final JsonBytesSqlTypeDescriptor INSTANCE = new JsonBytesSqlTypeDescriptor();

private static final Map<Class<? extends Dialect>, JsonBytesSqlTypeDescriptor> INSTANCE_MAP = new HashMap<Class<? extends Dialect>, JsonBytesSqlTypeDescriptor>();

static {
INSTANCE_MAP.put(H2Dialect.class, INSTANCE);
INSTANCE_MAP.put(Oracle8iDialect.class, new JsonBytesSqlTypeDescriptor(2016));
}

public static JsonBytesSqlTypeDescriptor of(Class<? extends Dialect> dialectClass) {
for (Map.Entry<Class<? extends Dialect>, JsonBytesSqlTypeDescriptor> instanceMapEntry : INSTANCE_MAP.entrySet()) {
if(instanceMapEntry.getKey().isAssignableFrom(dialectClass)) {
return instanceMapEntry.getValue();
}
}
return null;
}

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;
Expand Down
@@ -1,32 +1,42 @@
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.ParameterTypeUtils;
import com.vladmihalcea.hibernate.util.StringUtils;
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.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.BasicBinder;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.usertype.DynamicParameterizedType;
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) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {

sqlTypeDescriptor(st.getConnection()).getBinder(javaTypeDescriptor).bind(
st, value, index, options
);
Expand All @@ -50,7 +60,7 @@ protected Object extractJson(CallableStatement statement, String name) throws SQ
}

private AbstractJsonSqlTypeDescriptor sqlTypeDescriptor(Connection connection) {
if(sqlTypeDescriptor == null) {
if (sqlTypeDescriptor == null) {
sqlTypeDescriptor = resolveSqlTypeDescriptor(connection);
}
return sqlTypeDescriptor;
Expand All @@ -59,20 +69,51 @@ 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 Oracle8iDialect) {
if (properties != null) {
DynamicParameterizedType.ParameterType parameterType = ParameterTypeUtils.resolve(properties);
if (parameterType != null) {
String columnType = ParameterTypeUtils.getColumnType(parameterType);
if (!StringUtils.isBlank(columnType)) {
if (columnType.equals("json")) {
return JsonBytesSqlTypeDescriptor.of(dialect.getClass());
} else if (columnType.equals("blob") || columnType.equals("clob")) {
return JsonBlobSqlTypeDescriptor.INSTANCE;
} else if (columnType.equals("varchar2")) {
return JsonStringSqlTypeDescriptor.INSTANCE;
}
}
}
}
if (metaDataInfo.getDatabaseMajorVersion() >= 21) {
return JsonBytesSqlTypeDescriptor.of(dialect.getClass());
}
}
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);
}
}
}

0 comments on commit d20c417

Please sign in to comment.