Skip to content

Commit b9f86b7

Browse files
committed
Add support for Oracle 21c JSON columns #422
Add support for customizing the JsonType underlying Oracle column type #424
1 parent a2f92c0 commit b9f86b7

File tree

67 files changed

+4642
-189
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+4642
-189
lines changed

hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBlobType.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import com.vladmihalcea.hibernate.type.AbstractHibernateType;
5+
import com.vladmihalcea.hibernate.type.json.internal.JsonBlobSqlTypeDescriptor;
56
import com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor;
67
import com.vladmihalcea.hibernate.type.util.Configuration;
78
import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
@@ -33,50 +34,50 @@ public class JsonBlobType extends AbstractHibernateType<Object> implements Dynam
3334

3435
public JsonBlobType() {
3536
super(
36-
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
37+
JsonBlobSqlTypeDescriptor.INSTANCE,
3738
new JsonTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper())
3839
);
3940
}
4041

4142
public JsonBlobType(Type javaType) {
4243
super(
43-
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
44+
JsonBlobSqlTypeDescriptor.INSTANCE,
4445
new JsonTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper(), javaType)
4546
);
4647
}
4748

4849
public JsonBlobType(Configuration configuration) {
4950
super(
50-
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
51+
JsonBlobSqlTypeDescriptor.INSTANCE,
5152
new JsonTypeDescriptor(configuration.getObjectMapperWrapper()),
5253
configuration
5354
);
5455
}
5556

5657
public JsonBlobType(ObjectMapper objectMapper) {
5758
super(
58-
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
59+
JsonBlobSqlTypeDescriptor.INSTANCE,
5960
new JsonTypeDescriptor(new ObjectMapperWrapper(objectMapper))
6061
);
6162
}
6263

6364
public JsonBlobType(ObjectMapperWrapper objectMapperWrapper) {
6465
super(
65-
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
66+
JsonBlobSqlTypeDescriptor.INSTANCE,
6667
new JsonTypeDescriptor(objectMapperWrapper)
6768
);
6869
}
6970

7071
public JsonBlobType(ObjectMapper objectMapper, Type javaType) {
7172
super(
72-
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
73+
JsonBlobSqlTypeDescriptor.INSTANCE,
7374
new JsonTypeDescriptor(new ObjectMapperWrapper(objectMapper), javaType)
7475
);
7576
}
7677

7778
public JsonBlobType(ObjectMapperWrapper objectMapperWrapper, Type javaType) {
7879
super(
79-
org.hibernate.type.descriptor.sql.BlobTypeDescriptor.DEFAULT,
80+
JsonBlobSqlTypeDescriptor.INSTANCE,
8081
new JsonTypeDescriptor(objectMapperWrapper, javaType)
8182
);
8283
}

hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/json/JsonType.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
import com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor;
77
import com.vladmihalcea.hibernate.type.util.Configuration;
88
import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
9+
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
910
import org.hibernate.usertype.DynamicParameterizedType;
11+
import org.hibernate.usertype.ParameterizedType;
1012

13+
import javax.persistence.Column;
1114
import java.lang.reflect.Type;
1215
import java.util.Properties;
1316

@@ -16,24 +19,45 @@
1619
* {@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:
1720
* </p>
1821
* <ul>
19-
* <li><strong>PostgreSQL</strong> - for both <code>jsonb</code> and <code>json</code> column types</li>
20-
* <li><strong>MySQL</strong> - for the <code>json</code> column type</li>
21-
* <li><strong>SQL Server</strong> - for the <code>NVARCHAR</code> column type storing JSON</li>
22-
* <li><strong>Oracle</strong> - for the <code>VARCHAR</code> column type storing JSON</li>
23-
* <li><strong>H2</strong> - for the <code>json</code> column type</li>
22+
* <li><strong>PostgreSQL</strong> - for both <strong><code>jsonb</code></strong> and <strong><code>json</code></strong> column types</li>
23+
* <li><strong>MySQL</strong> - for the <strong><code>json</code></strong> column type</li>
24+
* <li><strong>SQL Server</strong> - for the <strong><code>NVARCHAR</code></strong> column type storing JSON</li>
25+
* <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>
26+
* <li><strong>H2</strong> - for the <strong><code>json</code></strong> column type</li>
2427
* </ul>
25-
*
28+
* <p>
29+
* 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
30+
* {@code VARCHAR2} or {@code BLOB} column types.
31+
* </p>
32+
* <p>
33+
* However, if you don't want to migrate to the new {@code JSON} data type,
34+
* then you just have to provide the column type via the JPA {@link Column#columnDefinition()} attribute,
35+
* like in the following example:
36+
* </p>
37+
* <pre>
38+
* {@code @Type(}type = "com.vladmihalcea.hibernate.type.json.JsonType")
39+
* {@code @Column(}columnDefinition = "VARCHAR2")
40+
* </pre>
2641
* <p>
2742
* 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>.
2843
* </p>
2944
* <p>
30-
* 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>.
45+
* 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>.
3146
* </p>
47+
* <p>
48+
* Or, you can use the {@link JsonType}, but you'll have to specify the underlying column type
49+
* using the JPA {@link Column#columnDefinition()} attribute, like this:
50+
* </p>
51+
* <pre>
52+
* {@code @Type(}type = "com.vladmihalcea.hibernate.type.json.JsonType")
53+
* {@code @Column(}columnDefinition = "BLOB")
54+
* private String properties;
55+
* </pre>
3256
*
3357
* @author Vlad Mihalcea
3458
*/
3559
public class JsonType
36-
extends AbstractHibernateType<Object> implements DynamicParameterizedType {
60+
extends AbstractHibernateType<Object> implements DynamicParameterizedType {
3761

3862
public static final JsonType INSTANCE = new JsonType();
3963

@@ -53,7 +77,7 @@ public JsonType(Type javaType) {
5377

5478
public JsonType(Configuration configuration) {
5579
super(
56-
new JsonSqlTypeDescriptor(),
80+
new JsonSqlTypeDescriptor(configuration.getProperties()),
5781
new JsonTypeDescriptor(configuration.getObjectMapperWrapper()),
5882
configuration
5983
);
@@ -94,6 +118,10 @@ public String getName() {
94118
@Override
95119
public void setParameterValues(Properties parameters) {
96120
((JsonTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters);
121+
SqlTypeDescriptor sqlTypeDescriptor = getSqlTypeDescriptor();
122+
if (sqlTypeDescriptor instanceof ParameterizedType) {
123+
ParameterizedType parameterizedType = (ParameterizedType) sqlTypeDescriptor;
124+
parameterizedType.setParameterValues(parameters);
125+
}
97126
}
98-
99127
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.vladmihalcea.hibernate.type.json.internal;
2+
3+
import org.hibernate.type.descriptor.ValueBinder;
4+
import org.hibernate.type.descriptor.ValueExtractor;
5+
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
6+
import org.hibernate.type.descriptor.sql.BlobTypeDescriptor;
7+
8+
/**
9+
* @author Vlad Mihalcea
10+
*/
11+
public class JsonBlobSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor {
12+
13+
public static final JsonBlobSqlTypeDescriptor INSTANCE = new JsonBlobSqlTypeDescriptor();
14+
15+
private BlobTypeDescriptor blobTypeDescriptor = BlobTypeDescriptor.DEFAULT;
16+
17+
@Override
18+
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
19+
return blobTypeDescriptor.getBinder(javaTypeDescriptor);
20+
}
21+
22+
@Override
23+
public int getSqlType() {
24+
return blobTypeDescriptor.getSqlType();
25+
}
26+
27+
@Override
28+
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
29+
return blobTypeDescriptor.getExtractor(javaTypeDescriptor);
30+
}
31+
}

hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonBytesSqlTypeDescriptor.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.vladmihalcea.hibernate.type.json.internal;
22

3+
import org.hibernate.dialect.Dialect;
4+
import org.hibernate.dialect.H2Dialect;
5+
import org.hibernate.dialect.Oracle8iDialect;
36
import org.hibernate.type.descriptor.ValueBinder;
47
import org.hibernate.type.descriptor.WrapperOptions;
58
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
69
import org.hibernate.type.descriptor.sql.BasicBinder;
710

811
import java.io.UnsupportedEncodingException;
912
import java.sql.*;
13+
import java.util.HashMap;
14+
import java.util.Map;
1015

1116
/**
1217
* @author Vlad Mihalcea
@@ -15,8 +20,34 @@ public class JsonBytesSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor {
1520

1621
public static final JsonBytesSqlTypeDescriptor INSTANCE = new JsonBytesSqlTypeDescriptor();
1722

23+
private static final Map<Class<? extends Dialect>, JsonBytesSqlTypeDescriptor> INSTANCE_MAP = new HashMap<Class<? extends Dialect>, JsonBytesSqlTypeDescriptor>();
24+
25+
static {
26+
INSTANCE_MAP.put(H2Dialect.class, INSTANCE);
27+
INSTANCE_MAP.put(Oracle8iDialect.class, new JsonBytesSqlTypeDescriptor(2016));
28+
}
29+
30+
public static JsonBytesSqlTypeDescriptor of(Class<? extends Dialect> dialectClass) {
31+
for (Map.Entry<Class<? extends Dialect>, JsonBytesSqlTypeDescriptor> instanceMapEntry : INSTANCE_MAP.entrySet()) {
32+
if(instanceMapEntry.getKey().isAssignableFrom(dialectClass)) {
33+
return instanceMapEntry.getValue();
34+
}
35+
}
36+
return null;
37+
}
38+
1839
public static final String CHARSET = "UTF8";
1940

41+
private final int jdbcType;
42+
43+
public JsonBytesSqlTypeDescriptor() {
44+
this.jdbcType = Types.BINARY;
45+
}
46+
47+
public JsonBytesSqlTypeDescriptor(int jdbcType) {
48+
this.jdbcType = jdbcType;
49+
}
50+
2051
@Override
2152
public int getSqlType() {
2253
return Types.BINARY;

hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonSqlTypeDescriptor.java

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
11
package com.vladmihalcea.hibernate.type.json.internal;
22

3-
import org.hibernate.dialect.Dialect;
4-
import org.hibernate.dialect.H2Dialect;
5-
import org.hibernate.dialect.PostgreSQL81Dialect;
3+
import com.vladmihalcea.hibernate.type.util.ParameterTypeUtils;
4+
import com.vladmihalcea.hibernate.util.StringUtils;
5+
import org.hibernate.dialect.*;
66
import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver;
77
import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter;
88
import org.hibernate.type.descriptor.ValueBinder;
99
import org.hibernate.type.descriptor.WrapperOptions;
1010
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
1111
import org.hibernate.type.descriptor.sql.BasicBinder;
12-
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
12+
import org.hibernate.usertype.DynamicParameterizedType;
13+
import org.hibernate.usertype.ParameterizedType;
1314

1415
import java.sql.*;
16+
import java.util.Properties;
1517

1618
/**
1719
* @author Vlad Mihalcea
1820
*/
19-
public class JsonSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor {
21+
public class JsonSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor implements ParameterizedType {
2022

2123
private volatile Dialect dialect;
2224
private volatile AbstractJsonSqlTypeDescriptor sqlTypeDescriptor;
2325

26+
private volatile Properties properties;
27+
28+
public JsonSqlTypeDescriptor() {
29+
}
30+
31+
public JsonSqlTypeDescriptor(Properties properties) {
32+
this.properties = properties;
33+
}
34+
2435
@Override
2536
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
2637
return new BasicBinder<X>(javaTypeDescriptor, this) {
2738
@Override
2839
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
29-
3040
sqlTypeDescriptor(st.getConnection()).getBinder(javaTypeDescriptor).bind(
3141
st, value, index, options
3242
);
@@ -50,7 +60,7 @@ protected Object extractJson(CallableStatement statement, String name) throws SQ
5060
}
5161

5262
private AbstractJsonSqlTypeDescriptor sqlTypeDescriptor(Connection connection) {
53-
if(sqlTypeDescriptor == null) {
63+
if (sqlTypeDescriptor == null) {
5464
sqlTypeDescriptor = resolveSqlTypeDescriptor(connection);
5565
}
5666
return sqlTypeDescriptor;
@@ -59,20 +69,51 @@ private AbstractJsonSqlTypeDescriptor sqlTypeDescriptor(Connection connection) {
5969
private AbstractJsonSqlTypeDescriptor resolveSqlTypeDescriptor(Connection connection) {
6070
try {
6171
StandardDialectResolver dialectResolver = new StandardDialectResolver();
62-
dialect = dialectResolver.resolveDialect(
63-
new DatabaseMetaDataDialectResolutionInfoAdapter(connection.getMetaData())
64-
);
65-
if(PostgreSQL81Dialect.class.isInstance(dialect)) {
72+
DatabaseMetaDataDialectResolutionInfoAdapter metaDataInfo = new DatabaseMetaDataDialectResolutionInfoAdapter(connection.getMetaData());
73+
dialect = dialectResolver.resolveDialect(metaDataInfo);
74+
if (dialect instanceof PostgreSQL81Dialect) {
6675
return JsonBinarySqlTypeDescriptor.INSTANCE;
67-
} else if(H2Dialect.class.isInstance(dialect)) {
76+
} else if (dialect instanceof H2Dialect) {
6877
return JsonBytesSqlTypeDescriptor.INSTANCE;
69-
} else {
70-
return JsonStringSqlTypeDescriptor.INSTANCE;
78+
} else if (dialect instanceof Oracle8iDialect) {
79+
if (properties != null) {
80+
DynamicParameterizedType.ParameterType parameterType = ParameterTypeUtils.resolve(properties);
81+
if (parameterType != null) {
82+
String columnType = ParameterTypeUtils.getColumnType(parameterType);
83+
if (!StringUtils.isBlank(columnType)) {
84+
if (columnType.equals("json")) {
85+
return JsonBytesSqlTypeDescriptor.of(dialect.getClass());
86+
} else if (columnType.equals("blob") || columnType.equals("clob")) {
87+
return JsonBlobSqlTypeDescriptor.INSTANCE;
88+
} else if (columnType.equals("varchar2")) {
89+
return JsonStringSqlTypeDescriptor.INSTANCE;
90+
}
91+
}
92+
}
93+
}
94+
if (metaDataInfo.getDatabaseMajorVersion() >= 21) {
95+
return JsonBytesSqlTypeDescriptor.of(dialect.getClass());
96+
}
7197
}
98+
return JsonStringSqlTypeDescriptor.INSTANCE;
7299
} catch (SQLException e) {
73100
throw new IllegalStateException(e);
74101
}
75102
}
76103

104+
@Override
105+
public int getSqlType() {
106+
return sqlTypeDescriptor != null ?
107+
sqlTypeDescriptor.getSqlType() :
108+
super.getSqlType();
109+
}
77110

111+
@Override
112+
public void setParameterValues(Properties parameters) {
113+
if (properties == null) {
114+
properties = parameters;
115+
} else {
116+
properties.putAll(parameters);
117+
}
118+
}
78119
}

0 commit comments

Comments
 (0)