HHH-15200 Add support for the SQLXML type

This commit is contained in:
Christian Beikov 2022-05-03 15:22:56 +02:00
parent d0b7d7f4b5
commit c18e611ed6
36 changed files with 1167 additions and 77 deletions

View File

@ -55,6 +55,11 @@ dependencies {
testImplementation testLibs.mockito
testImplementation testLibs.mockitoInline
testImplementation jakartaLibs.jaxbApi
testImplementation jakartaLibs.jaxb
testImplementation jakartaLibs.jsonb
testImplementation libs.jacksonXml
testRuntimeOnly testLibs.wildFlyTxnClient
testRuntimeOnly(libs.ehcache3) {
capabilities {

View File

@ -430,8 +430,22 @@ Can reference a
`FormatMapper` instance,
`FormatMapper` implementation `Class` reference,
`FormatMapper` implementation class name (fully-qualified class name) or
one of the following short hand constants `jackson` or `jsonb`.
By default the first of the possible providers that is available in the runtime is used, according to the listing order.
one of the following shorthand constants `jackson` or `jsonb`.
By default, the first of the possible providers that is available in the runtime is used, according to the listing order.
+
Note that the default serialization format of collections can differ depending on the serialization library.
`*hibernate.type.xml_format_mapper*` (e.g. A fully-qualified class name, an instance, or a `Class` object reference)::
Names a https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/FormatMapper.html[`FormatMapper`] implementation to be applied to the `SessionFactory` for XML serialization and deserialization.
+
Can reference a
`FormatMapper` instance,
`FormatMapper` implementation `Class` reference,
`FormatMapper` implementation class name (fully-qualified class name) or
one of the following shorthand constants `jackson-xml` or `jaxb`.
By default, the first of the possible providers that is available in the runtime is used, according to the listing order.
+
Note that the default serialization format of collections can differ depending on the serialization library.
[[configurations-bytecode-enhancement]]
=== Bytecode Enhancement Properties

View File

@ -1357,6 +1357,23 @@ include::{sourcedir}/basic/JsonMappingTests.java[tags=basic-json-example]
----
====
[[basic-mapping-xml]]
==== XML mapping
Hibernate will only use the `XML` type if explicitly configured through `@JdbcTypeCode( SqlTypes.SQLXML )`.
The XML library used for serialization/deserialization is detected automatically,
but can be overridden by setting `hibernate.type.xml_format_mapper`
as can be read in the <<appendices/Configurations.adoc#misc-options,Configurations>> section.
[[basic-xml-example]]
.Mapping XML
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/XmlMappingTests.java[tags=basic-xml-example]
----
====
[[basic-mapping-composition]]
==== Compositional basic mapping

View File

@ -6,21 +6,23 @@
*/
package org.hibernate.userguide.mapping.basic;
import java.util.List;
import java.util.Map;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
@ -36,40 +38,68 @@ import static org.hamcrest.Matchers.is;
*/
@DomainModel(annotatedClasses = JsonMappingTests.EntityWithJson.class)
@SessionFactory
public class JsonMappingTests {
public abstract class JsonMappingTests {
@ServiceRegistry(settings = @Setting(name = AvailableSettings.JSON_FORMAT_MAPPER, value = "jsonb"))
public static class JsonB extends JsonMappingTests {
public JsonB() {
super( true );
}
}
@ServiceRegistry(settings = @Setting(name = AvailableSettings.JSON_FORMAT_MAPPER, value = "jackson"))
public static class Jackson extends JsonMappingTests {
public Jackson() {
super( false );
}
}
private final boolean supportsObjectMapKey;
protected JsonMappingTests(boolean supportsObjectMapKey) {
this.supportsObjectMapKey = supportsObjectMapKey;
}
@Test
public void verifyMappings(SessionFactoryScope scope) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( EntityWithJson.class);
final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( EntityWithJson.class );
final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
final BasicAttributeMapping duration = (BasicAttributeMapping) entityDescriptor.findAttributeMapping("payload");
final JdbcMapping jdbcMapping = duration.getJdbcMapping();
assertThat(jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass(), equalTo(Map.class));
final JdbcType intervalType = jdbcTypeRegistry.getDescriptor(SqlTypes.JSON);
final JdbcType realType;
if (intervalType instanceof AdjustableJdbcType) {
realType = ((AdjustableJdbcType) intervalType).resolveIndicatedType(
() -> mappingMetamodel.getTypeConfiguration(),
jdbcMapping.getJavaTypeDescriptor()
);
}
else {
realType = intervalType;
}
assertThat( jdbcMapping.getJdbcType(), is( realType));
final BasicAttributeMapping payloadAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "payload" );
final BasicAttributeMapping objectMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "objectMap" );
final BasicAttributeMapping listAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "list" );
assertThat( payloadAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) );
assertThat( objectMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) );
assertThat( listAttribute.getJavaType().getJavaTypeClass(), equalTo( List.class ) );
final JdbcType jsonType = jdbcTypeRegistry.getDescriptor( SqlTypes.JSON );
assertThat( payloadAttribute.getJdbcMapping().getJdbcType(), is( jsonType ) );
assertThat( objectMapAttribute.getJdbcMapping().getJdbcType(), is( jsonType ) );
assertThat( listAttribute.getJdbcMapping().getJdbcType(), is( jsonType ) );
Map<String, String> stringMap = Map.of( "name", "ABC" );
Map<StringNode, StringNode> objectMap = supportsObjectMapKey ? Map.of( new StringNode( "name" ), new StringNode( "ABC" ) ) : null;
List<StringNode> list = List.of( new StringNode( "ABC" ) );
scope.inTransaction(
(session) -> {
session.persist( new EntityWithJson( 1, Map.of( "name", "ABC" ) ) );
session.persist( new EntityWithJson( 1, stringMap, objectMap, list ) );
}
);
scope.inTransaction(
(session) -> session.find( EntityWithJson.class, 1)
(session) -> {
EntityWithJson entityWithJson = session.find( EntityWithJson.class, 1 );
assertThat( entityWithJson.payload, is( stringMap ) );
assertThat( entityWithJson.objectMap, is( objectMap ) );
assertThat( entityWithJson.list, is( list ) );
}
);
}
@ -84,12 +114,62 @@ public class JsonMappingTests {
private Map<String, String> payload;
//end::basic-json-example[]
@JdbcTypeCode( SqlTypes.JSON )
private Map<StringNode, StringNode> objectMap;
@JdbcTypeCode( SqlTypes.JSON )
private List<StringNode> list;
public EntityWithJson() {
}
public EntityWithJson(Integer id, Map<String, String> payload) {
public EntityWithJson(
Integer id,
Map<String, String> payload,
Map<StringNode, StringNode> objectMap,
List<StringNode> list) {
this.id = id;
this.payload = payload;
this.objectMap = objectMap;
this.list = list;
}
}
public static class StringNode {
private String string;
public StringNode() {
}
public StringNode(String string) {
this.string = string;
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
StringNode that = (StringNode) o;
return string != null ? string.equals( that.string ) : that.string == null;
}
@Override
public int hashCode() {
return string != null ? string.hashCode() : 0;
}
}
}

View File

@ -0,0 +1,177 @@
/*
* 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.userguide.mapping.basic;
import java.util.List;
import java.util.Map;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = XmlMappingTests.EntityWithXml.class)
@SessionFactory
public abstract class XmlMappingTests {
@ServiceRegistry(settings = @Setting(name = AvailableSettings.XML_FORMAT_MAPPER, value = "jaxb"))
public static class Jaxb extends XmlMappingTests {
public Jaxb() {
super( true );
}
}
@ServiceRegistry(settings = @Setting(name = AvailableSettings.XML_FORMAT_MAPPER, value = "jackson-xml"))
public static class Jackson extends XmlMappingTests {
public Jackson() {
super( false );
}
}
private final boolean supportsObjectMapKey;
protected XmlMappingTests(boolean supportsObjectMapKey) {
this.supportsObjectMapKey = supportsObjectMapKey;
}
@Test
public void verifyMappings(SessionFactoryScope scope) {
final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( EntityWithXml.class);
final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry();
final BasicAttributeMapping stringMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "stringMap" );
final BasicAttributeMapping objectMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "objectMap" );
final BasicAttributeMapping listAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "list" );
assertThat( stringMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) );
assertThat( objectMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) );
assertThat( listAttribute.getJavaType().getJavaTypeClass(), equalTo( List.class ) );
final JdbcType xmlType = jdbcTypeRegistry.getDescriptor(SqlTypes.SQLXML);
assertThat( stringMapAttribute.getJdbcMapping().getJdbcType(), is( xmlType ) );
assertThat( objectMapAttribute.getJdbcMapping().getJdbcType(), is( xmlType ) );
assertThat( listAttribute.getJdbcMapping().getJdbcType(), is( xmlType ) );
Map<String, StringNode> stringMap = Map.of( "name", new StringNode( "ABC" ) );
Map<StringNode, StringNode> objectMap = supportsObjectMapKey ? Map.of( new StringNode( "name" ), new StringNode( "ABC" ) ) : null;
List<StringNode> list = List.of( new StringNode( "ABC" ) );
scope.inTransaction(
(session) -> {
session.persist( new EntityWithXml( 1, stringMap, objectMap, list ) );
}
);
scope.inTransaction(
(session) -> {
EntityWithXml entityWithXml = session.find( EntityWithXml.class, 1 );
assertThat( entityWithXml.stringMap, is( stringMap ) );
assertThat( entityWithXml.objectMap, is( objectMap ) );
assertThat( entityWithXml.list, is( list ) );
}
);
}
@Entity(name = "EntityWithXml")
@Table(name = "EntityWithXml")
public static class EntityWithXml {
@Id
private Integer id;
//tag::basic-xml-example[]
@JdbcTypeCode( SqlTypes.SQLXML )
private Map<String, StringNode> stringMap;
//end::basic-xml-example[]
@JdbcTypeCode( SqlTypes.SQLXML )
private Map<StringNode, StringNode> objectMap;
@JdbcTypeCode( SqlTypes.SQLXML )
private List<StringNode> list;
public EntityWithXml() {
}
public EntityWithXml(
Integer id,
Map<String, StringNode> stringMap,
Map<StringNode, StringNode> objectMap,
List<StringNode> list) {
this.id = id;
this.stringMap = stringMap;
this.objectMap = objectMap;
this.list = list;
}
}
@XmlRootElement(name = "stringNode")
public static class StringNode {
private String string;
public StringNode() {
}
public StringNode(String string) {
this.string = string;
}
@XmlElement
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
StringNode that = (StringNode) o;
return string != null ? string.equals( that.string ) : that.string == null;
}
@Override
public int hashCode() {
return string != null ? string.hashCode() : 0;
}
}
}

View File

@ -87,6 +87,8 @@ dependencies {
testRuntimeOnly dbLibs.informix
testRuntimeOnly dbLibs.cockroachdb
testRuntimeOnly dbLibs.oracle
testRuntimeOnly dbLibs.oracleXml
testRuntimeOnly dbLibs.oracleXmlParser
testRuntimeOnly dbLibs.sybase
// Since both the DB2 driver and HANA have a package "net.jpountz" we have to add dependencies conditionally

View File

@ -42,8 +42,9 @@ dependencies {
compileOnly jakartaLibs.jacc
compileOnly jakartaLibs.validation
compileOnly jakartaLibs.cdi
compileOnly jakartaLibs.jsonb
compileOnly jakartaLibs.jsonbApi
compileOnly libs.jackson
compileOnly libs.jacksonXml
testImplementation project(':hibernate-testing')
testImplementation project(':hibernate-ant')

View File

@ -72,6 +72,8 @@ import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.stat.Statistics;
import org.hibernate.type.FormatMapper;
import org.hibernate.type.JacksonJsonFormatMapper;
import org.hibernate.type.JacksonXmlFormatMapper;
import org.hibernate.type.JaxbXmlFormatMapper;
import org.hibernate.type.JsonBJsonFormatMapper;
import static org.hibernate.cfg.AvailableSettings.ALLOW_JTA_TRANSACTION_ACCESS;
@ -151,6 +153,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private Object beanManagerReference;
private Object validatorFactoryReference;
private FormatMapper jsonFormatMapper;
private FormatMapper xmlFormatMapper;
// SessionFactory behavior
private boolean jpaBootstrap;
@ -298,6 +301,10 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
configurationSettings.get( AvailableSettings.JSON_FORMAT_MAPPER ),
strategySelector
);
this.xmlFormatMapper = determineXmlFormatMapper(
configurationSettings.get( AvailableSettings.XML_FORMAT_MAPPER ),
strategySelector
);
this.sessionFactoryName = (String) configurationSettings.get( SESSION_FACTORY_NAME );
this.sessionFactoryNameAlsoJndiName = cfgService.getSetting(
@ -809,6 +816,32 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
);
}
private static FormatMapper determineXmlFormatMapper(Object setting, StrategySelector strategySelector) {
return strategySelector.resolveDefaultableStrategy(
FormatMapper.class,
setting,
(Callable<FormatMapper>) () -> {
try {
// Force initialization of the instance
JacksonXmlFormatMapper.INSTANCE.hashCode();
return JacksonXmlFormatMapper.INSTANCE;
}
catch (NoClassDefFoundError ex) {
// Ignore
}
try {
// Force initialization of the instance
JaxbXmlFormatMapper.INSTANCE.hashCode();
return JaxbXmlFormatMapper.INSTANCE;
}
catch (NoClassDefFoundError ex) {
// Ignore
}
return null;
}
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SessionFactoryOptionsState
@ -1218,7 +1251,13 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
public FormatMapper getJsonFormatMapper() {
return jsonFormatMapper;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public FormatMapper getXmlFormatMapper() {
return xmlFormatMapper;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// In-flight mutation access
public void applyBeanManager(Object beanManager) {

View File

@ -50,6 +50,7 @@ import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
import org.hibernate.type.descriptor.jdbc.XmlAsStringJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.DdlType;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
@ -387,6 +388,7 @@ public class MetadataBuildingProcess {
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.UUID, SqlTypes.BINARY );
}
jdbcTypeRegistry.addDescriptorIfAbsent( JsonJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( XmlAsStringJdbcType.INSTANCE );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY );
final int preferredSqlTypeCodeForDuration = ConfigurationHelper.getPreferredSqlTypeCodeForDuration( bootstrapContext.getServiceRegistry() );
if ( preferredSqlTypeCodeForDuration != SqlTypes.INTERVAL_SECOND ) {
@ -408,6 +410,13 @@ public class MetadataBuildingProcess {
dialect
)
);
ddlTypeRegistry.addDescriptorIfAbsent(
new DdlTypeImpl(
SqlTypes.SQLXML,
ddlTypeRegistry.getTypeName( SqlTypes.VARCHAR, null, null, null ),
dialect
)
);
// Fallback to the geometry DdlType when geography is requested
final DdlType geometryType = ddlTypeRegistry.getDescriptor( SqlTypes.GEOMETRY );
if ( geometryType != null ) {

View File

@ -40,6 +40,8 @@ import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoo
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder;
import org.hibernate.type.FormatMapper;
import org.hibernate.type.JacksonJsonFormatMapper;
import org.hibernate.type.JacksonXmlFormatMapper;
import org.hibernate.type.JaxbXmlFormatMapper;
import org.hibernate.type.JsonBJsonFormatMapper;
import org.jboss.logging.Logger;
@ -114,6 +116,7 @@ public class StrategySelectorBuilder {
addImplicitNamingStrategies( strategySelector );
addCacheKeysFactories( strategySelector );
addJsonFormatMappers( strategySelector );
addXmlFormatMappers( strategySelector );
// apply auto-discovered registrations
for ( StrategyRegistrationProvider provider : classLoaderService.loadJavaServices( StrategyRegistrationProvider.class ) ) {
@ -265,4 +268,17 @@ public class StrategySelectorBuilder {
JsonBJsonFormatMapper.class
);
}
private void addXmlFormatMappers(StrategySelectorImpl strategySelector) {
strategySelector.registerStrategyImplementor(
FormatMapper.class,
JacksonXmlFormatMapper.SHORT_NAME,
JacksonXmlFormatMapper.class
);
strategySelector.registerStrategyImplementor(
FormatMapper.class,
JaxbXmlFormatMapper.SHORT_NAME,
JaxbXmlFormatMapper.class
);
}
}

View File

@ -461,4 +461,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
public FormatMapper getJsonFormatMapper() {
return delegate.getJsonFormatMapper();
}
@Override
public FormatMapper getXmlFormatMapper() {
return delegate.getXmlFormatMapper();
}
}

View File

@ -314,4 +314,11 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy();
FormatMapper getJsonFormatMapper();
/**
* The format mapper to use for serializing/deserializing XML data.
*
* @since 6.0.1
*/
FormatMapper getXmlFormatMapper();
}

View File

@ -2562,15 +2562,31 @@ public interface AvailableSettings {
* <li>an instance of {@code FormatMapper},
* <li>a {@link Class} representing a class that implements {@code FormatMapper},
* <li>the name of a class that implements {@code FormatMapper}, or
* <li>one of the short hand constants {@code jackson} or {@code jsonb}.
* <li>one of the shorthand constants {@code jackson} or {@code jsonb}.
* </ul>
* By default, the first of the possible providers that is available in the runtime is
* By default, the first of the possible providers that is available at runtime is
* used, according to the listing order.
*
* @since 6.0
*/
String JSON_FORMAT_MAPPER = "hibernate.type.json_format_mapper";
/**
* Specifies a {@link org.hibernate.type.FormatMapper} used for XML serialization
* and deserialization, either:
* <ul>
* <li>an instance of {@code FormatMapper},
* <li>a {@link Class} representing a class that implements {@code FormatMapper},
* <li>the name of a class that implements {@code FormatMapper}, or
* <li>one of the shorthand constants {@code jackson} or {@code jaxb}.
* </ul>
* By default, the first of the possible providers that is available at runtime is
* used, according to the listing order.
*
* @since 6.0.1
*/
String XML_FORMAT_MAPPER = "hibernate.type.xml_format_mapper";
/**
* Specifies the default strategy for storage of the timezone information
* for zoned datetime types:

View File

@ -306,7 +306,7 @@ public class BasicValueBinder implements JdbcTypeIndicators {
// If we get into this method we know that there is a Java type for the value
// and that it is safe to load on the app classloader.
final Class<?> modelJavaType = resolveJavaType( modelTypeXClass );
final java.lang.reflect.Type modelJavaType = resolveJavaType( modelTypeXClass );
if ( modelJavaType == null ) {
throw new IllegalStateException( "BasicType requires Java type" );
}
@ -495,9 +495,10 @@ public class BasicValueBinder implements JdbcTypeIndicators {
else {
mapKeyClass = modelPropertyTypeXClass;
}
final Class<?> implicitJavaType = resolveJavaType( mapKeyClass );
final java.lang.reflect.Type javaType = resolveJavaType( mapKeyClass );
final Class<Object> javaTypeClass = ReflectHelper.getClass( javaType );
implicitJavaTypeAccess = (typeConfiguration) -> implicitJavaType;
implicitJavaTypeAccess = (typeConfiguration) -> javaType;
final MapKeyEnumerated mapKeyEnumeratedAnn = mapAttribute.getAnnotation( MapKeyEnumerated.class );
if ( mapKeyEnumeratedAnn != null ) {
@ -638,7 +639,8 @@ public class BasicValueBinder implements JdbcTypeIndicators {
XClass xclass = elementTypeXClass == null && attributeXProperty.isArray()
? attributeXProperty.getElementClass()
: elementTypeXClass;
Class<?> javaType = resolveJavaType( xclass );
final java.lang.reflect.Type javaType = resolveJavaType( xclass );
final Class<Object> javaTypeClass = ReflectHelper.getClass( javaType );
implicitJavaTypeAccess = typeConfiguration -> javaType;
@ -657,7 +659,7 @@ public class BasicValueBinder implements JdbcTypeIndicators {
temporalPrecision = null;
}
if ( javaType.isEnum() ) {
if ( javaTypeClass.isEnum() ) {
final Enumerated enumeratedAnn = attributeXProperty.getAnnotation( Enumerated.class );
if ( enumeratedAnn != null ) {
enumType = enumeratedAnn.value();
@ -697,7 +699,8 @@ public class BasicValueBinder implements JdbcTypeIndicators {
private void prepareBasicAttribute(String declaringClassName, XProperty attributeDescriptor, XClass attributeType) {
final Class<?> javaType = resolveJavaType( attributeType );
final java.lang.reflect.Type javaType = resolveJavaType( attributeType );
final Class<Object> javaTypeClass = ReflectHelper.getClass( javaType );
implicitJavaTypeAccess = typeConfiguration -> javaType;
@ -716,7 +719,7 @@ public class BasicValueBinder implements JdbcTypeIndicators {
this.temporalPrecision = null;
}
if ( javaType.isEnum() ) {
if ( javaTypeClass.isEnum() ) {
final Enumerated enumeratedAnn = attributeDescriptor.getAnnotation( Enumerated.class );
if ( enumeratedAnn != null ) {
this.enumType = enumeratedAnn.value();
@ -976,10 +979,10 @@ public class BasicValueBinder implements JdbcTypeIndicators {
return mutability;
}
private Class<?> resolveJavaType(XClass returnedClassOrElement) {
private java.lang.reflect.Type resolveJavaType(XClass returnedClassOrElement) {
return buildingContext.getBootstrapContext()
.getReflectionManager()
.toClass( returnedClassOrElement );
.getReflectionManager()
.toType( returnedClassOrElement );
}
private Dialect getDialect() {

View File

@ -56,6 +56,7 @@ import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.*;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.CapacityDependentDdlType;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
@ -163,6 +164,8 @@ public class DB2Dialect extends Dialect {
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "xml", this ) );
if ( getDB2Version().isBefore( 11 ) ) {
// should use 'binary' since version 11
ddlTypeRegistry.addDescriptor(
@ -638,6 +641,8 @@ public class DB2Dialect extends Dialect {
jdbcTypeRegistry.addDescriptor( Types.NVARCHAR, VarcharJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( Types.NUMERIC, DecimalJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE );
// DB2 requires a custom binder for binding untyped nulls that resolves the type through the statement
typeContributions.contributeJdbcType( ObjectNullResolvingJdbcType.INSTANCE );

View File

@ -599,6 +599,7 @@ public class OracleDialect extends Dialect {
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "SYS.XMLTYPE", this ) );
if ( getVersion().isSameOrAfter( 10 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "MDSYS.SDO_GEOMETRY", this ) );
}
@ -686,6 +687,8 @@ public class OracleDialect extends Dialect {
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
typeContributions.contributeJdbcType( OracleXmlJdbcType.INSTANCE );
if ( getVersion().isSameOrAfter( 12 ) ) {
// account for Oracle's deprecated support for LONGVARBINARY
// prefer BLOB, unless the user explicitly opts out

View File

@ -0,0 +1,43 @@
/*
* 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.SQLException;
import java.sql.Types;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.XmlJdbcType;
/**
* @author Christian Beikov
*/
public class OracleXmlJdbcType extends XmlJdbcType {
public static final OracleXmlJdbcType INSTANCE = new OracleXmlJdbcType();
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
// Seems the Oracle JDBC driver doesn't support `setNull(index, Types.SQLXML)`
// but it seems that the following works fine
return new XmlValueBinder<>( javaType, this ) {
@Override
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
st.setNull( index, Types.VARCHAR );
}
@Override
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
st.setNull( name, Types.VARCHAR );
}
};
}
}

View File

@ -72,6 +72,7 @@ import org.hibernate.type.descriptor.jdbc.InstantAsTimestampWithTimeZoneJdbcType
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectNullAsBinaryTypeJdbcType;
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
import org.hibernate.type.descriptor.jdbc.XmlJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.internal.Scale6IntervalSecondDdlType;
@ -99,6 +100,7 @@ import static org.hibernate.type.SqlTypes.NCHAR;
import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.OTHER;
import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
@ -207,6 +209,7 @@ public class PostgreSQLDialect extends Dialect {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) );
ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "xml", this ) );
if ( getVersion().isSameOrAfter( 8, 2 ) ) {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) );
@ -255,6 +258,9 @@ public class PostgreSQLDialect extends Dialect {
case "jsonb":
jdbcTypeCode = JSON;
break;
case "xml":
jdbcTypeCode = SQLXML;
break;
case "inet":
jdbcTypeCode = INET;
break;
@ -1094,6 +1100,7 @@ public class PostgreSQLDialect extends Dialect {
jdbcTypeRegistry.addDescriptor( Types.BLOB, BlobJdbcType.BLOB_BINDING );
jdbcTypeRegistry.addDescriptor( Types.CLOB, ClobJdbcType.CLOB_BINDING );
jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE );
if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) {
jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLInetJdbcType.INSTANCE );

View File

@ -129,27 +129,26 @@ public abstract class PostgreSQLPGObjectJdbcType implements JdbcType {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return ( (PostgreSQLPGObjectJdbcType) getJdbcType() ).fromString(
rs.getString( paramIndex ),
getJavaType(),
options
);
return getObject( rs.getString( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return ( (PostgreSQLPGObjectJdbcType) getJdbcType() ).fromString(
statement.getString( index ),
getJavaType(),
options
);
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 string, WrapperOptions options) throws SQLException {
if ( string == null ) {
return null;
}
return ( (PostgreSQLPGObjectJdbcType) getJdbcType() ).fromString(
statement.getString( name ),
string,
getJavaType(),
options
);

View File

@ -54,6 +54,7 @@ import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.SmallIntJdbcType;
import org.hibernate.type.descriptor.jdbc.XmlJdbcType;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
@ -173,6 +174,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) );
}
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "xml", this ) );
}
@Override
@ -211,6 +213,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
Types.TINYINT,
SmallIntJdbcType.INSTANCE
);
typeContributions.contributeJdbcType( XmlJdbcType.INSTANCE );
}
@Override

View File

@ -169,6 +169,7 @@ public final class FastSessionServices {
private final CacheRetrieveMode defaultCacheRetrieveMode;
private final ConnectionObserverStatsBridge defaultJdbcObservers;
private final FormatMapper jsonFormatMapper;
private final FormatMapper xmlFormatMapper;
FastSessionServices(SessionFactoryImpl sf) {
Objects.requireNonNull( sf );
@ -243,6 +244,7 @@ public final class FastSessionServices {
this.defaultLockOptions = initializeDefaultLockOptions( defaultSessionProperties );
this.initialSessionFlushMode = initializeDefaultFlushMode( defaultSessionProperties );
this.jsonFormatMapper = sessionFactoryOptions.getJsonFormatMapper();
this.xmlFormatMapper = sessionFactoryOptions.getXmlFormatMapper();
}
private static FlushMode initializeDefaultFlushMode(Map<String, Object> defaultSessionProperties) {
@ -374,4 +376,8 @@ public final class FastSessionServices {
public FormatMapper getJsonFormatMapper() {
return jsonFormatMapper;
}
public FormatMapper getXmlFormatMapper() {
return xmlFormatMapper;
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.type;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
/**
* @author Christian Beikov
*/
public class JacksonXmlFormatMapper implements FormatMapper {
public static final String SHORT_NAME = "jackson-xml";
public static final JacksonXmlFormatMapper INSTANCE = new JacksonXmlFormatMapper();
private final ObjectMapper objectMapper;
public JacksonXmlFormatMapper() {
this(new XmlMapper());
}
public JacksonXmlFormatMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
try {
return objectMapper.readValue( charSequence.toString(), objectMapper.constructType( javaType.getJavaType() ) );
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType, e );
}
}
@Override
public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions) {
try {
return objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) )
.writeValueAsString( value );
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType, e );
}
}
}

View File

@ -0,0 +1,259 @@
/*
* 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.type;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.annotation.XmlAnyElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* @author Christian Beikov
*/
public class JaxbXmlFormatMapper implements FormatMapper {
public static final String SHORT_NAME = "jaxb";
public static final JaxbXmlFormatMapper INSTANCE = new JaxbXmlFormatMapper();
public JaxbXmlFormatMapper() {
}
@Override
public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
try {
if ( Map.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
final JAXBContext context;
final Class<Object> keyClass;
final Class<Object> valueClass;
if ( javaType.getJavaType() instanceof ParameterizedType ) {
final Type[] typeArguments = ( (ParameterizedType) javaType.getJavaType() ).getActualTypeArguments();
keyClass = ReflectHelper.getClass( typeArguments[0] );
valueClass = ReflectHelper.getClass( typeArguments[1] );
context = JAXBContext.newInstance( MapWrapper.class, keyClass, valueClass );
}
else {
keyClass = Object.class;
valueClass = Object.class;
context = JAXBContext.newInstance( MapWrapper.class );
}
final Unmarshaller unmarshaller = context.createUnmarshaller();
final MapWrapper mapWrapper = (MapWrapper) unmarshaller
.unmarshal( new StringReader( charSequence.toString() ) );
final List<Object> elements = mapWrapper.elements;
final Map<Object, Object> map = CollectionHelper.linkedMapOfSize( elements.size() >> 1 );
for ( int i = 0; i < elements.size(); i += 2 ) {
final Object keyElement = unmarshaller.unmarshal( (Node) elements.get( i ), keyClass ).getValue();
final Object valueElement = unmarshaller.unmarshal( (Node) elements.get( i + 1 ), valueClass )
.getValue();
final Object key;
final Object value;
if ( keyElement instanceof Element ) {
key = ( (Element) keyElement ).getFirstChild().getTextContent();
}
else {
key = keyElement;
}
if ( valueElement instanceof Element ) {
value = ( (Element) valueElement ).getFirstChild().getTextContent();
}
else {
value = valueElement;
}
map.put( key, value );
}
//noinspection unchecked
return (T) map;
}
else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
final JAXBContext context;
final Class<Object> valueClass;
if ( javaType.getJavaType() instanceof ParameterizedType ) {
final Type[] typeArguments = ( (ParameterizedType) javaType.getJavaType() ).getActualTypeArguments();
valueClass = ReflectHelper.getClass( typeArguments[0] );
context = JAXBContext.newInstance( CollectionWrapper.class, valueClass );
}
else {
valueClass = Object.class;
context = JAXBContext.newInstance( CollectionWrapper.class );
}
final Unmarshaller unmarshaller = context.createUnmarshaller();
final CollectionWrapper collectionWrapper = (CollectionWrapper) unmarshaller
.unmarshal( new StringReader( charSequence.toString() ) );
final List<Object> elements = collectionWrapper.elements;
final Collection<Object> collection = new ArrayList<>( elements.size() >> 1 );
for ( int i = 0; i < elements.size(); i++ ) {
final Object valueElement = unmarshaller.unmarshal( (Node) elements.get( i ), valueClass )
.getValue();
final Object value;
if ( valueElement instanceof Element ) {
value = ( (Element) valueElement ).getFirstChild().getTextContent();
}
else {
value = valueElement;
}
collection.add( value );
}
//noinspection unchecked
return (T) collection;
}
else {
final JAXBContext context = JAXBContext.newInstance( javaType.getJavaTypeClass() );
//noinspection unchecked
return (T) context.createUnmarshaller().unmarshal( new StringReader( charSequence.toString() ) );
}
}
catch (JAXBException e) {
throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType, e );
}
}
@Override
public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions) {
try {
final StringWriter stringWriter = new StringWriter();
if ( Map.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
final JAXBContext context;
final Class<Object> keyClass;
final Class<Object> valueClass;
final MapWrapper mapWrapper = new MapWrapper();
final Map<?, ?> map = (Map<?, ?>) value;
if ( javaType.getJavaType() instanceof ParameterizedType ) {
final Type[] typeArguments = ( (ParameterizedType) javaType.getJavaType() ).getActualTypeArguments();
keyClass = ReflectHelper.getClass( typeArguments[0] );
valueClass = ReflectHelper.getClass( typeArguments[1] );
context = JAXBContext.newInstance( MapWrapper.class, keyClass, valueClass );
}
else {
if ( map.isEmpty() ) {
keyClass = Object.class;
valueClass = Object.class;
context = JAXBContext.newInstance( MapWrapper.class );
}
else {
final Map.Entry<?, ?> firstEntry = map.entrySet().iterator().next();
//noinspection unchecked
keyClass = (Class<Object>) firstEntry.getKey().getClass();
//noinspection unchecked
valueClass = (Class<Object>) firstEntry.getValue().getClass();
context = JAXBContext.newInstance( MapWrapper.class, keyClass, valueClass );
}
}
for ( Map.Entry<?, ?> entry : map.entrySet() ) {
mapWrapper.elements.add(
new JAXBElement<>(
new QName( "key" ),
keyClass,
entry.getKey()
)
);
mapWrapper.elements.add(
new JAXBElement<>(
new QName( "value" ),
valueClass,
entry.getValue()
)
);
}
createMarshaller( context ).marshal( mapWrapper, stringWriter );
}
else if ( Collection.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) {
final JAXBContext context;
final Class<Object> valueClass;
final Collection<?> collection = (Collection<?>) value;
final CollectionWrapper collectionWrapper = new CollectionWrapper( new ArrayList<>( collection ) );
if ( javaType.getJavaType() instanceof ParameterizedType ) {
final Type[] typeArguments = ( (ParameterizedType) javaType.getJavaType() ).getActualTypeArguments();
valueClass = ReflectHelper.getClass( typeArguments[0] );
context = JAXBContext.newInstance( CollectionWrapper.class, valueClass );
}
else {
if ( collection.isEmpty() ) {
valueClass = Object.class;
context = JAXBContext.newInstance( CollectionWrapper.class );
}
else {
//noinspection unchecked
valueClass = (Class<Object>) collection.iterator().next().getClass();
context = JAXBContext.newInstance( CollectionWrapper.class, valueClass );
}
}
// for ( Object element : collection ) {
// collectionWrapper.elements.add(
// new JAXBElement<>(
// new QName( "key" ),
// keyClass,
// entry.getKey()
// )
// );
// collectionWrapper.elements.add(
// new JAXBElement<>(
// new QName( "value" ),
// valueClass,
// entry.getValue()
// )
// );
// }
createMarshaller( context ).marshal( collectionWrapper, stringWriter );
}
else {
final JAXBContext context = JAXBContext.newInstance( javaType.getJavaTypeClass() );
createMarshaller( context ).marshal( value, stringWriter );
}
return stringWriter.toString();
}
catch (JAXBException e) {
throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType, e );
}
}
private Marshaller createMarshaller(JAXBContext context) throws JAXBException {
final Marshaller marshaller = context.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FRAGMENT, true );
return marshaller;
}
@XmlRootElement(name = "Map")
public static class MapWrapper {
@XmlAnyElement
List<Object> elements = new ArrayList<>();
}
@XmlRootElement(name = "Collection")
public static class CollectionWrapper {
@XmlAnyElement
List<Object> elements = new ArrayList<>();
public CollectionWrapper() {
}
public CollectionWrapper(List<Object> elements) {
this.elements = elements;
}
}
}

View File

@ -461,6 +461,8 @@ public class SqlTypes {
* The constant in the Java programming language, sometimes referred to
* as a type code, that identifies the generic SQL type
* {@code GEOGRAPHY}.
*
* @since 6.0.1
*/
public static final int GEOGRAPHY = 3250;

View File

@ -7,6 +7,7 @@
package org.hibernate.type.descriptor.java;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Comparator;
import java.util.Objects;
@ -248,4 +249,12 @@ public interface JavaType<T> extends Serializable {
default String getCheckCondition(String columnName, JdbcType sqlType, Dialect dialect) {
return null;
}
/**
* Creates the {@link JavaType} for the given {@link ParameterizedType} based on this {@link JavaType} registered
* for the raw type.
*/
default JavaType<T> createJavaType(ParameterizedType parameterizedType) {
return this;
}
}

View File

@ -6,10 +6,15 @@
*/
package org.hibernate.type.descriptor.java.spi;
import java.lang.reflect.ParameterizedType;
import org.hibernate.collection.spi.CollectionSemantics;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -41,6 +46,13 @@ public class CollectionJavaType<C> extends AbstractClassJavaType<C> {
return null;
}
@Override
public JavaType<C> createJavaType(ParameterizedType parameterizedType) {
//noinspection unchecked
// Construct a basic java type that knows its parametrization
return new UnknownBasicJavaType<>( parameterizedType, (MutabilityPlan<C>) MutableMutabilityPlan.INSTANCE );
}
@Override
public C fromString(CharSequence string) {
throw new UnsupportedOperationException();

View File

@ -132,17 +132,15 @@ public class JavaTypeRegistry implements JavaTypeBaseline.BaselineTarget, Serial
return resolveDescriptor(
javaType,
() -> {
final Class<?> javaTypeClass;
if ( javaType instanceof Class<?> ) {
javaTypeClass = (Class<?>) javaType;
}
else {
if ( javaType instanceof ParameterizedType ) {
final ParameterizedType parameterizedType = (ParameterizedType) javaType;
javaTypeClass = (Class<?>) parameterizedType.getRawType();
final JavaType<J> rawType = findDescriptor( ( parameterizedType ).getRawType() );
if ( rawType != null ) {
return rawType.createJavaType( parameterizedType );
}
}
return RegistryHelper.INSTANCE.createTypeDescriptor(
javaTypeClass,
javaType,
() -> {
final MutabilityPlan<J> determinedPlan = RegistryHelper.INSTANCE.determineMutabilityPlan( javaType, typeConfiguration );
if ( determinedPlan != null ) {

View File

@ -102,7 +102,7 @@ public class RegistryHelper {
return new SerializableJavaType( javaTypeClass, plan );
}
return new JavaTypeBasicAdaptor<>( javaTypeClass, plan );
return new UnknownBasicJavaType<>( javaType, plan );
}
private <J> Class<J> determineJavaTypeClass(Type javaType) {

View File

@ -0,0 +1,71 @@
/*
* 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.type.descriptor.java.spi;
import java.lang.reflect.Type;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractJavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
/**
* AbstractBasicTypeDescriptor adapter for cases where we do not know a proper JavaType
* for a given Java type.
*/
public class UnknownBasicJavaType<T> extends AbstractJavaType<T> {
public UnknownBasicJavaType(Class<T> type) {
super( type );
}
public UnknownBasicJavaType(Class<T> type, MutabilityPlan<T> mutabilityPlan) {
super( type, mutabilityPlan );
}
public UnknownBasicJavaType(Type type, MutabilityPlan<T> mutabilityPlan) {
super( type, mutabilityPlan );
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators context) {
throw new JdbcTypeRecommendationException(
"Could not determine recommended JdbcType for `" + getJavaType().getTypeName() + "`"
);
}
@Override
public String toString(T value) {
return value.toString();
}
@Override
public T fromString(CharSequence string) {
throw new UnsupportedOperationException(
"Conversion from String strategy not known for this Java type : " + getJavaType().getTypeName()
);
}
@Override
public <X> X unwrap(T value, Class<X> type, WrapperOptions options) {
throw new UnsupportedOperationException(
"Unwrap strategy not known for this Java type : " + getJavaType().getTypeName()
);
}
@Override
public <X> T wrap(X value, WrapperOptions options) {
throw new UnsupportedOperationException(
"Wrap strategy not known for this Java type : " + getJavaType().getTypeName()
);
}
@Override
public String toString() {
return "BasicJavaType(" + getJavaType().getTypeName() + ")";
}
}

View File

@ -30,7 +30,7 @@ public class JsonJdbcType implements JdbcType {
@Override
public int getJdbcTypeCode() {
return SqlTypes.OTHER;
return SqlTypes.VARCHAR;
}
@Override
@ -75,26 +75,25 @@ public class JsonJdbcType implements JdbcType {
return new BasicExtractor<>( javaType, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return options.getSessionFactory().getFastSessionServices().getJsonFormatMapper().fromString(
rs.getString( paramIndex ),
getJavaType(),
options
);
return getObject( rs.getString( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return options.getSessionFactory().getFastSessionServices().getJsonFormatMapper().fromString(
statement.getString( index ),
getJavaType(),
options
);
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(
statement.getString( name ),
json,
getJavaType(),
options
);

View File

@ -0,0 +1,104 @@
/*
* 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.type.descriptor.jdbc;
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;
/**
* Specialized type mapping for {@code SQLXML} and the XML SQL data type.
*
* @author Christian Beikov
*/
public class XmlAsStringJdbcType implements JdbcType {
/**
* Singleton access
*/
public static final XmlAsStringJdbcType INSTANCE = new XmlAsStringJdbcType();
@Override
public int getJdbcTypeCode() {
return SqlTypes.VARCHAR;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.SQLXML;
}
@Override
public String toString() {
return "XmlAsStringJdbcType";
}
@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 xml = options.getSessionFactory().getFastSessionServices().getXmlFormatMapper().toString(
value,
getJavaType(),
options
);
st.setString( index, xml );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final String xml = options.getSessionFactory().getFastSessionServices().getXmlFormatMapper().toString(
value,
getJavaType(),
options
);
st.setString( name, xml );
}
};
}
@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 xml, WrapperOptions options) throws SQLException {
if ( xml == null ) {
return null;
}
return options.getSessionFactory().getFastSessionServices().getXmlFormatMapper().fromString(
xml,
getJavaType(),
options
);
}
};
}
}

View File

@ -0,0 +1,109 @@
/*
* 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.type.descriptor.jdbc;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLXML;
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;
/**
* Specialized type mapping for {@code SQLXML} and the XML SQL data type.
*
* @author Christian Beikov
*/
public class XmlJdbcType implements JdbcType {
/**
* Singleton access
*/
public static final XmlJdbcType INSTANCE = new XmlJdbcType();
@Override
public int getJdbcTypeCode() {
return SqlTypes.SQLXML;
}
@Override
public String toString() {
return "XmlJdbcType";
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new XmlValueBinder<>( javaType, this );
}
@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.getSQLXML( paramIndex ), options );
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return getObject( statement.getSQLXML( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return getObject( statement.getSQLXML( name ), options );
}
private X getObject(SQLXML sqlxml, WrapperOptions options) throws SQLException {
if ( sqlxml == null ) {
return null;
}
return options.getSessionFactory().getFastSessionServices().getXmlFormatMapper().fromString(
sqlxml.getString(),
getJavaType(),
options
);
}
};
}
protected static class XmlValueBinder<X> extends BasicBinder<X> {
public XmlValueBinder(JavaType<X> javaType, JdbcType jdbcType) {
super( javaType, jdbcType );
}
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
final String xml = options.getSessionFactory().getFastSessionServices().getXmlFormatMapper().toString(
value,
getJavaType(),
options
);
SQLXML sqlxml = st.getConnection().createSQLXML();
sqlxml.setString( xml );
st.setSQLXML( index, sqlxml );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final String xml = options.getSessionFactory().getFastSessionServices().getXmlFormatMapper().toString(
value,
getJavaType(),
options
);
SQLXML sqlxml = st.getConnection().createSQLXML();
sqlxml.setString( xml );
st.setSQLXML( name, sqlxml );
}
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.access.spi.GetterFieldImpl;
import org.hibernate.property.access.spi.GetterMethodImpl;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException;
import org.hibernate.testing.ServiceRegistryBuilder;
import org.hibernate.testing.TestForIssue;
@ -58,7 +59,7 @@ public class AccessMappingTest {
sf = cfg.buildSessionFactory( serviceRegistry );
fail( "@Id and @OneToMany are not placed consistently in test entities. SessionFactory creation should fail." );
}
catch (MappingException e) {
catch (MappingException | JdbcTypeRecommendationException e) {
// success
}
finally {

View File

@ -19,6 +19,9 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
@ -31,10 +34,16 @@ public class TestDataReader {
}
List<TestDataElement> testDataElements = new ArrayList<TestDataElement>();
SAXReader reader = new SAXReader();
// Make sure we use the "right" implementation as the Oracle XDB driver comes with a JAXP implementation as well
SAXParserFactory factory = SAXParserFactory.newInstance( "org.apache.xerces.jaxp.SAXParserFactoryImpl", null );
factory.setValidating(false);
factory.setNamespaceAware(true);
try {
reader.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false );
reader.setFeature( "http://xml.org/sax/features/external-general-entities", false );
reader.setFeature( "http://xml.org/sax/features/external-parameter-entities", false );
factory.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false );
factory.setFeature( "http://xml.org/sax/features/external-general-entities", false );
factory.setFeature( "http://xml.org/sax/features/external-parameter-entities", false );
SAXParser parser = factory.newSAXParser();
reader.setXMLReader( parser.getXMLReader() );
Document document = reader.read( getInputStream( fileName ) );
addDataElements( document, testDataElements );
}

View File

@ -139,7 +139,7 @@ dependencies {
javadocClasspath jakartaLibs.validation
javadocClasspath jakartaLibs.cdi
javadocClasspath jakartaLibs.jacc
javadocClasspath jakartaLibs.jsonb
javadocClasspath jakartaLibs.jsonbApi
javadocClasspath libs.ant
javadocClasspath dbLibs.postgresql
javadocClasspath libs.jackson

View File

@ -83,6 +83,7 @@ dependencyResolutionManagement {
alias( "classmate" ).to( "com.fasterxml", "classmate" ).version( "1.5.1" )
alias( "jackson" ).to ( "com.fasterxml.jackson.core", "jackson-databind" ).version( "2.13.0" )
alias( "jacksonXml" ).to ( "com.fasterxml.jackson.dataformat", "jackson-dataformat-xml" ).version( "2.13.0" )
alias( "validator" ).to( "org.hibernate.validator", "hibernate-validator" ).version( "7.0.4.Final" )
alias( "ant" ).to( "org.apache.ant", "ant" ).version( "1.8.2" )
@ -103,6 +104,7 @@ dependencyResolutionManagement {
}
jakartaLibs {
version( "jaxbRuntime", "3.0.2" )
version( "jsonbRuntime", "2.0.4" )
// `jakartaJpaVersion` comes from the `` plugin - accounting for command-line overriding of the JPA version to use
alias( "jpa" ).to( "jakarta.persistence", "jakarta.persistence-api" ).version( "${jakartaJpaVersion}" )
@ -110,7 +112,8 @@ dependencyResolutionManagement {
alias( "validation" ).to( "jakarta.validation", "jakarta.validation-api" ).version( "3.0.0" )
alias( "jacc" ).to( "jakarta.authorization", "jakarta.authorization-api" ).version( "2.0.0" )
alias( "cdi" ).to( "jakarta.enterprise", "jakarta.enterprise.cdi-api" ).version( "3.0.0" )
alias( "jsonb" ).to( "jakarta.json.bind", "jakarta.json.bind-api" ).version( "2.0.0" )
alias( "jsonbApi" ).to( "jakarta.json.bind", "jakarta.json.bind-api" ).version( "2.0.0" )
alias( "jsonb" ).to( "org.eclipse", "yasson" ).versionRef( "jsonbRuntime" )
alias( "inject" ).to( "jakarta.inject", "jakarta.inject-api" ).version( "2.0.0" )
alias( "jaxbApi" ).to( "jakarta.xml.bind", "jakarta.xml.bind-api" ).version( "3.0.1" )
alias( "jaxb" ).to( "org.glassfish.jaxb", "jaxb-runtime" ).versionRef( "jaxbRuntime" )
@ -160,6 +163,7 @@ dependencyResolutionManagement {
version( "h2", "2.1.210" )
version( "pgsql", "42.2.16" )
version( "mysql", "8.0.27" )
version( "oracle", "21.3.0.0" )
alias( "h2" ).to( "com.h2database", "h2" ).versionRef( "h2" )
alias( "h2gis" ).to( "org.orbisgis", "h2gis" ).version( "2.0.0" )
@ -170,7 +174,9 @@ dependencyResolutionManagement {
alias( "mysql" ).to( "mysql", "mysql-connector-java" ).versionRef( "mysql" )
alias( "tidb" ).to( "mysql", "mysql-connector-java" ).versionRef( "mysql" )
alias( "mariadb" ).to( "org.mariadb.jdbc", "mariadb-java-client" ).version( "2.2.3" )
alias( "oracle" ).to( "com.oracle.database.jdbc", "ojdbc8" ).version( "21.3.0.0" )
alias( "oracle" ).to( "com.oracle.database.jdbc", "ojdbc8" ).versionRef( "oracle" )
alias( "oracleXml" ).to( "com.oracle.database.xml", "xdb" ).versionRef( "oracle" )
alias( "oracleXmlParser" ).to( "com.oracle.database.xml", "xmlparserv2" ).versionRef( "oracle" )
alias( "mssql" ).to( "com.microsoft.sqlserver", "mssql-jdbc" ).version( "7.2.1.jre8" )
alias( "db2" ).to( "com.ibm.db2", "jcc" ).version( "11.5.7.0" )
alias( "hana" ).to( "com.sap.cloud.db.jdbc", "ngdbc" ).version( "2.4.59" )