Skip to content

Commit

Permalink
HHH-15748 Use JSON DDL type on Oracle 21+ and BLOB on 12+
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Nov 25, 2022
1 parent 5b5721f commit 276b7a6
Show file tree
Hide file tree
Showing 17 changed files with 352 additions and 16 deletions.
4 changes: 4 additions & 0 deletions docker_db.sh
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ alter database drop logfile group 1;
alter database drop logfile group 2;
alter database drop logfile group 3;
alter system set open_cursors=1000 sid='*' scope=both;
create user hibernate_orm_test identified by hibernate_orm_test quota unlimited on users;
grant all privileges to hibernate_orm_test;
EOF\""
}

Expand Down Expand Up @@ -502,6 +504,8 @@ alter system set open_cursors=1000 sid='*' scope=both;
alter system set processes=150 scope=spfile;
alter system set filesystemio_options=asynch scope=spfile;
alter system set disk_asynch_io=true scope=spfile;
create user hibernate_orm_test identified by hibernate_orm_test quota unlimited on users;
grant all privileges to hibernate_orm_test;
EOF\""
echo "Waiting for Oracle to restart after configuration..."
$CONTAINER_CLI stop oracle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/
package org.hibernate.userguide.mapping.basic;

import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.sql.Clob;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -107,13 +110,33 @@ public void verifyMappings(SessionFactoryScope scope) {
assertThat( entityWithJson.objectMap, is( objectMap ) );
assertThat( entityWithJson.list, is( list ) );
assertThat( entityWithJson.jsonString, isOneOf( json, alternativeJson ) );
String nativeJson = session.createNativeQuery(
Object nativeJson = session.createNativeQuery(
"select jsonString from EntityWithJson",
String.class
Object.class
)
.getResultList()
.get( 0 );
assertThat( nativeJson, isOneOf( json, alternativeJson ) );
final String jsonText;
try {
if ( nativeJson instanceof Blob ) {
final Blob blob = (Blob) nativeJson;
jsonText = new String(
blob.getBytes( 1L, (int) blob.length() ),
StandardCharsets.UTF_8
);
}
else if ( nativeJson instanceof Clob ) {
final Clob jsonClob = (Clob) nativeJson;
jsonText = jsonClob.getSubString( 1L, (int) jsonClob.length() );
}
else {
jsonText = (String) nativeJson;
}
}
catch (Exception e) {
throw new RuntimeException( e );
}
assertThat( jsonText, isOneOf( json, alternativeJson ) );
}
);
}
Expand Down
4 changes: 2 additions & 2 deletions gradle/databases.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ ext {
oracle_ci : [
'db.dialect' : 'org.hibernate.dialect.OracleDialect',
'jdbc.driver': 'oracle.jdbc.OracleDriver',
'jdbc.user' : 'SYSTEM',
'jdbc.pass' : 'Oracle18',
'jdbc.user' : 'hibernate_orm_test',
'jdbc.pass' : 'hibernate_orm_test',
'jdbc.url' : 'jdbc:oracle:thin:@' + dbHost + ':1521:XE',
'connection.init_sql' : ''
],
Expand Down
2 changes: 2 additions & 0 deletions gradle/java-module.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ dependencies {
implementation libs.logging

compileOnly libs.loggingAnnotations
// Used for compiling some Oracle specific JdbcTypes
compileOnly dbLibs.oracle

// JUnit dependencies made up of:
// * JUnit 5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleArrayJdbcType;
import org.hibernate.dialect.OracleTypes;
import org.hibernate.dialect.OracleTypesHelper;
import org.hibernate.dialect.OracleXmlJdbcType;
import org.hibernate.dialect.Replacer;
Expand Down Expand Up @@ -85,6 +86,7 @@
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonBlobJdbcType;
import org.hibernate.type.descriptor.jdbc.NullJdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
Expand All @@ -109,6 +111,7 @@
import static org.hibernate.type.SqlTypes.DECIMAL;
import static org.hibernate.type.SqlTypes.GEOMETRY;
import static org.hibernate.type.SqlTypes.INTEGER;
import static org.hibernate.type.SqlTypes.JSON;
import static org.hibernate.type.SqlTypes.NUMERIC;
import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.REAL;
Expand Down Expand Up @@ -632,6 +635,12 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "SYS.XMLTYPE", this ) );
if ( getVersion().isSameOrAfter( 10 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "MDSYS.SDO_GEOMETRY", this ) );
if ( getVersion().isSameOrAfter( 21 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
}
else if ( getVersion().isSameOrAfter( 12 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "blob", this ) );
}
}
}

Expand Down Expand Up @@ -669,6 +678,8 @@ public JdbcType resolveSqlTypeDescriptor(
int scale,
JdbcTypeRegistry jdbcTypeRegistry) {
switch ( jdbcTypeCode ) {
case OracleTypes.JSON:
return jdbcTypeRegistry.getDescriptor( JSON );
case Types.NUMERIC:
if ( scale == -127 ) {
// For some reason, the Oracle JDBC driver reports FLOAT
Expand Down Expand Up @@ -744,6 +755,13 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
BlobJdbcType.DEFAULT;

typeContributions.contributeJdbcType( descriptor );

if ( getVersion().isSameOrAfter( 21 ) ) {
typeContributions.contributeJdbcType( OracleTypesHelper.INSTANCE.getJsonJdbcType() );
}
else {
typeContributions.contributeJdbcType( JsonBlobJdbcType.INSTANCE );
}
}

typeContributions.contributeJdbcType( OracleArrayJdbcType.INSTANCE );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonBlobJdbcType;
import org.hibernate.type.descriptor.jdbc.NullJdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
Expand Down Expand Up @@ -608,6 +609,12 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR

ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "SYS.XMLTYPE", this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "MDSYS.SDO_GEOMETRY", this ) );
if ( getVersion().isSameOrAfter( 21 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
}
else if ( getVersion().isSameOrAfter( 12 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "blob", this ) );
}
}

@Override
Expand Down Expand Up @@ -643,6 +650,8 @@ public JdbcType resolveSqlTypeDescriptor(
int scale,
JdbcTypeRegistry jdbcTypeRegistry) {
switch ( jdbcTypeCode ) {
case OracleTypes.JSON:
return jdbcTypeRegistry.getDescriptor( JSON );
case Types.NUMERIC:
if ( scale == -127 ) {
// For some reason, the Oracle JDBC driver reports FLOAT
Expand Down Expand Up @@ -718,6 +727,13 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
BlobJdbcType.DEFAULT;

typeContributions.contributeJdbcType( descriptor );

if ( getVersion().isSameOrAfter( 21 ) ) {
typeContributions.contributeJdbcType( OracleTypesHelper.INSTANCE.getJsonJdbcType() );
}
else {
typeContributions.contributeJdbcType( JsonBlobJdbcType.INSTANCE );
}
}

typeContributions.contributeJdbcType( OracleArrayJdbcType.INSTANCE );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.type.SqlTypes;
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.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;

import oracle.jdbc.OracleType;

/**
* Specialized type mapping for {@code JSON} that encodes as OSON.
* This class is used from {@link OracleTypesHelper} reflectively to avoid loading Oracle JDBC classes eagerly.
*
* @author Christian Beikov
*/
public class OracleJsonJdbcType implements JdbcType {
/**
* Singleton access
*/
public static final OracleJsonJdbcType INSTANCE = new OracleJsonJdbcType();

private static final int JSON_TYPE_CODE = OracleType.JSON.getVendorTypeNumber();

@Override
public int getJdbcTypeCode() {
return SqlTypes.BLOB;
}

@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.JSON;
}

@Override
public String toString() {
return "OracleJsonJdbcType";
}

@Override
public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) {
// No literal support for now
return null;
}

@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
final String json = options.getSessionFactory().getFastSessionServices().getJsonFormatMapper().toString(
value,
getJavaType(),
options
);
st.setObject( index, json, JSON_TYPE_CODE );
}

@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final String json = options.getSessionFactory().getFastSessionServices().getJsonFormatMapper().toString(
value,
getJavaType(),
options
);
st.setObject( name, json, JSON_TYPE_CODE );
}
};
}

@Override
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return getObject( rs.getString( paramIndex ), options );
}

@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return getObject( statement.getString( index ), options );
}

@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return getObject( statement.getString( name ), options );
}

private X getObject(String json, WrapperOptions options) throws SQLException {
if ( json == null ) {
return null;
}
return options.getSessionFactory().getFastSessionServices().getJsonFormatMapper().fromString(
json,
getJavaType(),
options
);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.dialect;

/**
* The Oracle specific JDBC type code.
*/
public class OracleTypes {
public static final int JSON = 2016;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import org.hibernate.HibernateException;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;

import org.jboss.logging.Logger;

Expand All @@ -27,8 +29,10 @@ public class OracleTypesHelper {

private static final String ORACLE_TYPES_CLASS_NAME = "oracle.jdbc.OracleTypes";
private static final String DEPRECATED_ORACLE_TYPES_CLASS_NAME = "oracle.jdbc.driver.OracleTypes";
private static final String ORACLE_JSON_JDBC_TYPE_CLASS_NAME = "org.hibernate.dialect.OracleJsonJdbcType";

private final int oracleCursorTypeSqlType;
private final JdbcType jsonJdbcType;

private OracleTypesHelper() {
int typeCode = -99;
Expand All @@ -39,6 +43,17 @@ private OracleTypesHelper() {
log.warn( "Unable to resolve Oracle CURSOR JDBC type code: the class OracleTypesHelper was initialized but the Oracle JDBC driver could not be loaded." );
}
oracleCursorTypeSqlType = typeCode;

JdbcType jsonJdbcType = JsonJdbcType.INSTANCE;
try {
jsonJdbcType = (JdbcType) ReflectHelper.classForName( ORACLE_JSON_JDBC_TYPE_CLASS_NAME )
.getField( "INSTANCE" )
.get( null );
}
catch (Exception e) {
log.warn( "Unable to resolve OracleJsonJdbcType: the class OracleTypesHelper was initialized but the Oracle JDBC driver could not be loaded." );
}
this.jsonJdbcType = jsonJdbcType;
}

private int extractOracleCursorTypeValue() {
Expand Down Expand Up @@ -75,6 +90,10 @@ public int getOracleCursorTypeSqlType() {
return oracleCursorTypeSqlType;
}

public JdbcType getJsonJdbcType() {
return jsonJdbcType;
}

// initial code as copied from Oracle8iDialect
//
// private int oracleCursorTypeSqlType = INIT_ORACLETYPES_CURSOR_VALUE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ public NativeQueryImplementor createNativeQuery(String sqlString, Class resultCl
else if ( getFactory().getMappingMetamodel().isEntityClass(resultClass) ) {
query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
}
else {
else if ( resultClass != Object.class && resultClass != Object[].class ) {
query.addScalar( 1, resultClass );
}
return query;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ private static ConcurrentHashMap<Integer, Class> buildJdbcTypeCodeToJavaClassMap
workMap.put( SqlTypes.ROWID, RowId.class );
workMap.put( SqlTypes.SQLXML, SQLXML.class );
workMap.put( SqlTypes.UUID, UUID.class );
workMap.put( SqlTypes.JSON, String.class );
workMap.put( SqlTypes.INET, InetAddress.class );
workMap.put( SqlTypes.TIMESTAMP_UTC, Instant.class );
workMap.put( SqlTypes.INTERVAL_SECOND, Duration.class );
Expand Down

0 comments on commit 276b7a6

Please sign in to comment.