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 b9f86b7
Show file tree
Hide file tree
Showing 67 changed files with 4,642 additions and 189 deletions.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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,45 @@
* {@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")
* private String properties;
* </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 +77,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 +118,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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit b9f86b7

Please sign in to comment.