Migrate BasicType and UserType User Guide examples to unit tests

This commit is contained in:
Vlad Mihalcea 2016-03-02 14:38:24 +02:00
parent 58c6c7a98a
commit 2abcab0501
27 changed files with 991 additions and 233 deletions

View File

@ -16,7 +16,7 @@ With the coming of JPA, most of this information is now defined in a way that is
This chapter will focus on JPA mapping where possible. This chapter will focus on JPA mapping where possible.
For Hibernate mapping features not supported by JPA we will prefer Hibernate extension annotations. For Hibernate mapping features not supported by JPA we will prefer Hibernate extension annotations.
include::mapping_types.adoc[] include::types.adoc[]
include::naming.adoc[] include::naming.adoc[]
include::basic_types.adoc[] include::basic_types.adoc[]
include::embeddables.adoc[] include::embeddables.adoc[]

View File

@ -93,19 +93,21 @@ Strictly speaking, a basic type is denoted with with the `javax.persistence.Basi
Generally speaking the `@Basic` annotation can be ignored, as it is assumed by default. Generally speaking the `@Basic` annotation can be ignored, as it is assumed by default.
Both of the following examples are ultimately the same. Both of the following examples are ultimately the same.
.With `@Basic` [[basic-annotation-explicit-example]]
.`@Basic` declared explicitly
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/ex1.java[] include::{sourcedir}/basic/ExplicitBasicTypeTest.java[tags=basic-annotation-explicit-example]
---- ----
==== ====
.Without `@Basic` [[basic-annotation-implicit-example]]
.`@Basic` being implicitly implied
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/ex2.java[] include::{sourcedir}/basic/ImplicitBasicTypeTest.java[tags=basic-annotation-implicit-example]
---- ----
==== ====
@ -142,6 +144,7 @@ JPA says that EAGER is a requirement to the provider (Hibernate) that the value
Hibernate ignores this setting for basic types unless you are using bytecode enhancement. Hibernate ignores this setting for basic types unless you are using bytecode enhancement.
See the <chapters/pc/BytecodeEnhancement.adoc#BytecodeEnhancement,BytecodeEnhancement>> for additional information on fetching and on bytecode enhancement. See the <chapters/pc/BytecodeEnhancement.adoc#BytecodeEnhancement,BytecodeEnhancement>> for additional information on fetching and on bytecode enhancement.
[[basic-column-annotation]]
==== The `@Column` annotation ==== The `@Column` annotation
JPA defines rules for implicitly determining the name of tables and columns. JPA defines rules for implicitly determining the name of tables and columns.
@ -150,11 +153,12 @@ For a detailed discussion of implicit naming see <<naming.adoc#naming,Naming>>.
For basic type attributes, the implicit naming rule is that the column name is the same as the attribute name. For basic type attributes, the implicit naming rule is that the column name is the same as the attribute name.
If that implicit naming rule does not meet your requirements, you can explicitly tell Hibernate (and other providers) the column name to use. If that implicit naming rule does not meet your requirements, you can explicitly tell Hibernate (and other providers) the column name to use.
[[basic-annotation-explicit-column-example]]
.Explicit column naming .Explicit column naming
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/ExplicitColumnNaming.java[] include::{sourcedir}/basic/ExplicitColumnNamingTest.java[tags=basic-annotation-explicit-column-example]
---- ----
==== ====
@ -172,7 +176,7 @@ or its `org.hibernate.type.IntegerType` for mapping `java.lang.Integer` attribut
The answer lies in a service inside Hibernate called the `org.hibernate.type.BasicTypeRegistry`, which essentially maintains a map of `org.hibernate.type.BasicType` (a `org.hibernate.type.Type` specialization) instances keyed by a name. The answer lies in a service inside Hibernate called the `org.hibernate.type.BasicTypeRegistry`, which essentially maintains a map of `org.hibernate.type.BasicType` (a `org.hibernate.type.Type` specialization) instances keyed by a name.
We will see later, in the <<basic-explicit>> section, that we can explicitly tell Hibernate which BasicType to use for a particular attribute. We will see later, in the <<basic-type-annotation>> section, that we can explicitly tell Hibernate which BasicType to use for a particular attribute.
But first let's explore how implicit resolution works and how applications can adjust implicit resolution. But first let's explore how implicit resolution works and how applications can adjust implicit resolution.
[NOTE] [NOTE]
@ -191,9 +195,9 @@ So that is the baseline mapping within `BasicTypeRegistry` for Strings.
Applications can also extend (add new `BasicType` registrations) or override (replace an exiting `BasicType` registration) using one of the Applications can also extend (add new `BasicType` registrations) or override (replace an exiting `BasicType` registration) using one of the
`MetadataBuilder#applyBasicType` methods or the `MetadataBuilder#applyTypes` method during bootstrap. `MetadataBuilder#applyBasicType` methods or the `MetadataBuilder#applyTypes` method during bootstrap.
For more details, see <<basic-custom>> section. For more details, see <<basic-custom-type>> section.
[[basic-explicit]] [[basic-type-annotation]]
==== Explicit BasicTypes ==== Explicit BasicTypes
Sometimes you want a particular attribute to be handled differently. Sometimes you want a particular attribute to be handled differently.
@ -201,11 +205,12 @@ Occasionally Hibernate will implicitly pick a `BasicType` that you do not want (
In these cases you must explicitly tell Hibernate the `BasicType` to use, via the `org.hibernate.annotations.Type` annotation. In these cases you must explicitly tell Hibernate the `BasicType` to use, via the `org.hibernate.annotations.Type` annotation.
[[basic-type-annotation-example]]
.Using `@org.hibernate.annotations.Type` .Using `@org.hibernate.annotations.Type`
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/explicitType.java[] include::{sourcedir}/basic/ExplicitTypeTest.java[tags=basic-type-annotation-example]
---- ----
==== ====
@ -218,49 +223,176 @@ The `org.hibernate.annotations.Type#type` attribute can name any of the followin
* Fully qualified name of any `org.hibernate.type.Type` implementation * Fully qualified name of any `org.hibernate.type.Type` implementation
* Any key registered with `BasicTypeRegistry` * Any key registered with `BasicTypeRegistry`
* The name of any known "type definitions" * The name of any known _type definitions_
[[basic-custom]] [[basic-custom-type]]
==== Custom BasicTypes ==== Custom BasicTypes
Hibernate makes it relatively easy for developers to create their own basic type mappings type. Hibernate makes it relatively easy for developers to create their own basic type mappings type.
For example, you might want to persist properties of type `java.lang.BigInteger` to `VARCHAR` columns, or support completely new types. For example, you might want to persist properties of type `java.util.BigInteger` to `VARCHAR` columns, or support completely new types.
There are two approaches to developing a custom BasicType. There are two approaches to developing a custom type:
As a means of illustrating the different approaches, let's consider a use case where we need to support a class called `Fizzywig` from a third party library.
Let's assume that Fizzywig naturally stores as a VARCHAR.
The first approach is to directly implement the BasicType interface. - implementing a `BasicType` and registering it
- implement a `UserType` which doesn't require type registration
.Custom BasicType implementation As a means of illustrating the different approaches, let's consider a use case where we need to support a `java.util.BitSet` mapping that's stored as a VARCHAR.
[[basic-custom-type-BasicType]]
===== Implementing a `BasicType`
The first approach is to directly implement the `BasicType` interface.
[NOTE]
====
Because the `BasicType` interface has a lot of methods to implement, it's much more convenient to extend the `AbstractStandardBasicType`,
or the `AbstractSingleColumnStandardBasicType` if the value is stored in a single database column.
==== ====
[source,java]
----
include::{extrasdir}/basic/FizzywigType1.java[]
----
[source,java] First, we need to extend the `AbstractSingleColumnStandardBasicType` like this:
[[basic-custom-type-BitSetType-example]]
.Custom `BasicType` implementation
====
[source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/FizzywigType1_reg.java[] include::{sourcedir}/basic/BitSetType.java[tags=basic-custom-type-BitSetType-example]
---- ----
==== ====
The second approach is to implement the UserType interface. The `AbstractSingleColumnStandardBasicType` requires an `sqlTypeDescriptor` and a `javaTypeDescriptor`.
The `sqlTypeDescriptor` is `VarcharTypeDescriptor.INSTANCE` because the database column is a VARCHAR.
On the Java side, we need to use a `BitSetTypeDescriptor` instance which can be implemented like this:
.Custom UserType implementation [[basic-custom-type-BitSetTypeDescriptor-example]]
.Custom `AbstractTypeDescriptor` implementation
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/FizzywigType2.java[] include::{sourcedir}/basic/BitSetTypeDescriptor.java[tags=basic-custom-type-BitSetTypeDescriptor-example]
----
[source,java]
----
include::{extrasdir}/basic/FizzywigType2_reg.java[]
---- ----
==== ====
For additional information on developing and registering custom types, see the Hibernate Integration Guide. The `unwrap` method is used when passing a `BitSet` as a `PreparedStatement` bind parameter, while the `wrap` method is used to transform the JDBC column value object (e.g. `String` in our case) to the actual mapping object type (e.g. `BitSet` in this example).
The `BasicType` must be registered, and this can be done at bootstrapping time:
[[basic-custom-type-register-BasicType-example]]
.Register a Custom `BasicType` implementation
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-register-BasicType-example]
----
or using the `MetadataBuilder`
[source, JAVA, indent=0]
----
include::{sourcedir}/../bootstrap/BootstrapTest.java[tags=basic-custom-type-register-BasicType-example]
----
====
With the new `BitSetType` being registered as `bitset`, the entity mapping looks like this:
[[basic-custom-type-BitSetType-mapping-example]]
.Custom `BasicType` mapping
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-BitSetType-mapping-example]
----
====
To validate this new `BasicType` implementation, we can test it as follows:
[[basic-custom-type-BitSetType-persistence-example]]
.Persisting the custom `BasicType`
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetTypeTest.java[tags=basic-custom-type-BitSetType-persistence-example]
----
====
When executing this unit test, Hibernate generates the following SQL statements:
[[basic-custom-type-BitSetType-persistence-sql-example]]
.Persisting the custom `BasicType`
====
[source, JAVA, indent=0]
----
include::{extrasdir}/basic/basic-custom-type-BitSetType-persistence-sql-example.sql[]
----
====
As you can see, the `BitSetType` takes care of the _Java-to-SQL_ and _SQL-to-Java_ type conversion.
[[basic-custom-type-UserType]]
===== Implementing a `UserType`
The second approach is to implement the `UserType` interface.
[[basic-custom-type-BitSetUserType-example]]
.Custom `UserType` implementation
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetUserType.java[tags=basic-custom-type-BitSetUserType-example]
----
====
The entity mapping looks as follows:
[[basic-custom-type-BitSetUserType-mapping-example]]
.Custom `UserType` mapping
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetUserTypeTest.java[tags=basic-custom-type-BitSetUserType-mapping-example]
----
====
In this example, the `UserType` is registered under the `bitset` name, and this is done like this:
[[basic-custom-type-register-UserType-example]]
.Register a Custom `UserType` implementation
====
[source, JAVA, indent=0]
----
include::{sourcedir}/basic/BitSetUserTypeTest.java[tags=basic-custom-type-register-UserType-example]
----
or using the `MetadataBuilder`
[source, JAVA, indent=0]
----
include::{sourcedir}/../bootstrap/BootstrapTest.java[tags=basic-custom-type-register-UserType-example]
----
====
[NOTE]
====
Like `BasicType`, you can also register the `UserType` using a simple name.
Without registration, the `UserType` mapping requires the fully-classified name:
[source, JAVA, indent=0]
----
@Type( type = "org.hibernate.userguide.mapping.basic.BitSetUserType" )
----
====
When running the previous test case against the `BitSetUserType` entity mapping, Hibernate executed the following SQL statements:
[[basic-custom-type-BitSetUserType-persistence-sql-example]]
.Persisting the custom `BasicType`
====
[source, JAVA, indent=0]
----
include::{extrasdir}/basic/basic-custom-type-BitSetUserType-persistence-sql-example.sql[]
----
====
[[basic-enums]] [[basic-enums]]
==== Mapping enums ==== Mapping enums
@ -276,7 +408,7 @@ The original JPA-compliant way to map enums was via the `@Enumerated` and `@MapK
.`@Enumerated(ORDINAL)` example .`@Enumerated(ORDINAL)` example
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/EnumeratedOrdinal.java[] include::{extrasdir}/basic/EnumeratedOrdinal.java[]
---- ----
@ -290,7 +422,7 @@ In the ORDINAL example, the gender column is defined as an (nullable) INTEGER ty
.`@Enumerated(STRING)` example .`@Enumerated(STRING)` example
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/EnumeratedString.java[] include::{extrasdir}/basic/EnumeratedString.java[]
---- ----
@ -310,7 +442,7 @@ Let's revisit the Gender enum example, but instead we want to store the more sta
.Enum mapping with AttributeConverter example .Enum mapping with AttributeConverter example
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/EnumAttributeConverter.java[] include::{extrasdir}/basic/EnumAttributeConverter.java[]
---- ----
@ -334,7 +466,7 @@ Let's again revisit the Gender enum example, this time using a custom Type to st
.Enum mapping with custom Type example .Enum mapping with custom Type example
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/EnumCustomType.java[] include::{extrasdir}/basic/EnumCustomType.java[]
---- ----
@ -346,7 +478,7 @@ Again, the gender column is defined as a CHAR type and would hold:
* `'M'` - MALE * `'M'` - MALE
* `'F'` - FEMALE * `'F'` - FEMALE
For additional details on using custom types, see <<basic-custom>> section.. For additional details on using custom types, see <<basic-custom-type>> section..
[[basic-lob]] [[basic-lob]]
==== Mapping LOBs ==== Mapping LOBs
@ -385,7 +517,7 @@ Let's first map this using the JDBC locator.
.CLOB - locator mapping .CLOB - locator mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/ClobLocator.java[] include::{extrasdir}/basic/ClobLocator.java[]
---- ----
@ -395,7 +527,7 @@ We could also map a materialized form.
.CLOB - materialized mapping .CLOB - materialized mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/ClobMaterialized.java[] include::{extrasdir}/basic/ClobMaterialized.java[]
---- ----
@ -413,7 +545,7 @@ We might even want the materialized data as a char array (for some crazy reason)
.CLOB - materialized char[] mapping .CLOB - materialized char[] mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/ClobMaterializedCharArray.java[] include::{extrasdir}/basic/ClobMaterializedCharArray.java[]
---- ----
@ -433,7 +565,7 @@ Let's first map this using the JDBC locator.
.BLOB - locator mapping .BLOB - locator mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/BlobLocator.java[] include::{extrasdir}/basic/BlobLocator.java[]
---- ----
@ -443,7 +575,7 @@ We could also map a materialized BLOB form.
.BLOB - materialized mapping .BLOB - materialized mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/BlobMaterialized.java[] include::{extrasdir}/basic/BlobMaterialized.java[]
---- ----
@ -464,7 +596,7 @@ To map a specific attribute to a nationalized variant data type, Hibernate defin
.NVARCHAR mapping .NVARCHAR mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/NVARCHAR.java[] include::{extrasdir}/basic/NVARCHAR.java[]
---- ----
@ -472,7 +604,7 @@ include::{extrasdir}/basic/NVARCHAR.java[]
.NCLOB (locator) mapping .NCLOB (locator) mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/NCLOB_locator.java[] include::{extrasdir}/basic/NCLOB_locator.java[]
---- ----
@ -480,7 +612,7 @@ include::{extrasdir}/basic/NCLOB_locator.java[]
.NCLOB (materialized) mapping .NCLOB (materialized) mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/NCLOB_materialized.java[] include::{extrasdir}/basic/NCLOB_materialized.java[]
---- ----
@ -551,7 +683,7 @@ Considering the following entity:
.`java.util.Date` mapping .`java.util.Date` mapping
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/DateTemporal.java[] include::{extrasdir}/basic/DateTemporal.java[]
---- ----
@ -560,7 +692,7 @@ include::{extrasdir}/basic/DateTemporal.java[]
When persisting such entity: When persisting such entity:
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
DateEvent dateEvent = new DateEvent(new Date()); DateEvent dateEvent = new DateEvent(new Date());
entityManager.persist(dateEvent); entityManager.persist(dateEvent);
@ -583,7 +715,7 @@ Only the year, month and the day field were saved into the the database.
If we change the `@Temporal` type to TIME: If we change the `@Temporal` type to TIME:
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
@Temporal(TemporalType.TIME) @Temporal(TemporalType.TIME)
private Date timestamp; private Date timestamp;
@ -604,7 +736,7 @@ VALUES ( '16:51:58', 1 )
When the the `@Temporal` type is set to TIMESTAMP: When the the `@Temporal` type is set to TIMESTAMP:
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
@Temporal(TemporalType.TIMESTAMP) @Temporal(TemporalType.TIMESTAMP)
private Date timestamp; private Date timestamp;
@ -665,7 +797,7 @@ org.hibernate.AnnotationException: @Temporal should only be set on a java.util.D
[[basic-jpa-convert]] [[basic-jpa-convert]]
==== JPA 2.1 AttributeConverters ==== JPA 2.1 AttributeConverters
Although Hibernate has long been offering <<basic-custom,custom types>>, as a JPA 2.1 provider, Although Hibernate has long been offering <<basic-custom-type,custom types>>, as a JPA 2.1 provider,
it also supports `AttributeConverter`s as well. it also supports `AttributeConverter`s as well.
With a custom `AttributeConverter`, the application developer can map a given JDBC type to an entity basic type. With a custom `AttributeConverter`, the application developer can map a given JDBC type to an entity basic type.
@ -674,7 +806,7 @@ In the following example, the `java.util.Period` is going to be mapped to a `VAR
.`java.util.Period` custom `AttributeConverter` .`java.util.Period` custom `AttributeConverter`
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/PeriodStringConverter.java[] include::{extrasdir}/basic/PeriodStringConverter.java[]
---- ----
@ -684,7 +816,7 @@ To make use of this custom converter, the `@Convert` annotation must decorate th
.Entity using the custom `AttributeConverter` .Entity using the custom `AttributeConverter`
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
include::{extrasdir}/basic/PeriodStringConvert.java[] include::{extrasdir}/basic/PeriodStringConvert.java[]
---- ----
@ -709,7 +841,7 @@ This is usually double quotes, but the SQL Server uses brackets and MySQL uses b
.Quoting column names .Quoting column names
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
@Entity @Table(name="`Line Item`") @Entity @Table(name="`Line Item`")
class LineItem { class LineItem {
@ -811,7 +943,7 @@ For example, if your database provides a set of data encryption functions, you c
.`@ColumnTransformer` example .`@ColumnTransformer` example
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
@Entity @Entity
class CreditCard { class CreditCard {
@ -838,7 +970,7 @@ If a property uses more than one column, you must use the `forColumn` attribute
.`@ColumnTransformer` `forColumn` attribute usage .`@ColumnTransformer` `forColumn` attribute usage
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
@Entity @Entity
class User { class User {
@ -877,7 +1009,7 @@ You can use a SQL fragment (aka formula) instead of mapping a property into a co
.`@Formula` mapping usage .`@Formula` mapping usage
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
@Formula("obj_length * obj_height * obj_width") @Formula("obj_length * obj_height * obj_width")
private long objectVolume; private long objectVolume;
@ -911,7 +1043,7 @@ You must specify the mapping from values of the metaType to class names.
.`@Any` mapping usage .`@Any` mapping usage
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
@Any( @Any(
metaColumn = @Column( name = "property_type" ), metaColumn = @Column( name = "property_type" ),
@ -934,7 +1066,7 @@ Note that `@AnyDef` can be mutualized and reused. It is recommended to place it
.`@AnyMetaDef` mapping usage .`@AnyMetaDef` mapping usage
==== ====
[source,java] [source, JAVA, indent=0]
---- ----
//on a package //on a package
@AnyMetaDef( name="property" @AnyMetaDef( name="property"

View File

@ -1,17 +0,0 @@
@Entity
public class Product {
@Id
@Basic
private Integer id;
@Basic
private String sku;
@Basic
private String name;
@Basic
@Column( name = "NOTES" )
private String description;
}

View File

@ -1,44 +0,0 @@
public class FizzywigType1 implements org.hibernate.type.BasicType {
public static final FizzywigType1 INSTANCE = new FizzywigType1();
@Override
public String[] getRegistrationKeys() {
return new String[]{Fizzywig.class.getName()};
}
@Override
public int[] sqlTypes( Mapping mapping ) {
return new int[]{java.sql.Types.VARCHAR};
}
@Override
public Class getReturnedClass() {
return Money.class;
}
@Override
public Object nullSafeGet(
ResultSet rs,
String[] names,
SessionImplementor session,
Object owner) throws SQLException {
return Fizzwig.fromString(
StringType.INSTANCE.get( rs, names[0], sesson )
);
}
@Override
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
boolean[] settable,
SessionImplementor session) throws SQLException {
final String dbValue = value == null
? null
: (( Fizzywig ) value).asString();
StringType.INSTANCE.nullSafeSet( st, value, index, settable, session );
}
...
}

View File

@ -1,3 +0,0 @@
MetadataSources metadataSources = ...;
metadataSources.getMetaDataBuilder().applyBasicType( FizzwigType1.INSTANCE );
...

View File

@ -1,40 +0,0 @@
public class FizzywigType2 implements org.hibernate.usertype.UserType {
public static final String KEYS = new String[]{Fizzywig.class.getName()};
public static final FizzywigType1 INSTANCE = new FizzywigType1();
@Override
public int[] sqlTypes( Mapping mapping ) {
return new int[]{java.sql.Types.VARCHAR};
}
@Override
public Class getReturnedClass() {
return Fizzywig.class;
}
@Override
public Object nullSafeGet(
ResultSet rs,
String[] names,
SessionImplementor session,
Object owner) throws SQLException {
return Fizzwig.fromString(
StringType.INSTANCE.get( rs, names[0], sesson )
);
}
@Override
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
SessionImplementor session) throws SQLException {
final String dbValue = value == null
? null
: (( Fizzywig ) value).asString();
StringType.INSTANCE.nullSafeSet( st, value, index, session );
}
...
}

View File

@ -1,3 +0,0 @@
MetadataSources metadataSources = ...;
metadataSources.getMetaDataBuilder().applyBasicType( FizzwigType2.KEYS,FizzwigType2.INSTANCE )
...

View File

@ -0,0 +1,22 @@
DEBUG SQL:92 -
insert
into
Product
(bitSet, id)
values
(?, ?)
TRACE BasicBinder:65 - binding parameter [1] as [VARCHAR] - [{0, 65, 128, 129}]
TRACE BasicBinder:65 - binding parameter [2] as [INTEGER] - [1]
DEBUG SQL:92 -
select
bitsettype0_.id as id1_0_0_,
bitsettype0_.bitSet as bitSet2_0_0_
from
Product bitsettype0_
where
bitsettype0_.id=?
TRACE BasicBinder:65 - binding parameter [1] as [INTEGER] - [1]
TRACE BasicExtractor:61 - extracted value ([bitSet2_0_0_] : [VARCHAR]) - [{0, 65, 128, 129}]

View File

@ -0,0 +1,22 @@
DEBUG SQL:92 -
insert
into
Product
(bitSet, id)
values
(?, ?)
DEBUG BitSetUserType:71 - Binding 1,10,11 to parameter 1
TRACE BasicBinder:65 - binding parameter [2] as [INTEGER] - [1]
DEBUG SQL:92 -
select
bitsetuser0_.id as id1_0_0_,
bitsetuser0_.bitSet as bitSet2_0_0_
from
Product bitsetuser0_
where
bitsetuser0_.id=?
TRACE BasicBinder:65 - binding parameter [1] as [INTEGER] - [1]
DEBUG BitSetUserType:56 - Result set column bitSet2_0_0_ value is 1,10,11

View File

@ -1,16 +0,0 @@
@Entity
public class Product {
@Id
@Basic
private Integer id;
@Basic
private String sku;
@Basic
private String name;
@Basic
private String description;
}

View File

@ -1,8 +0,0 @@
@Entity
public class Product {
@Id
private Integer id;
private String sku;
private String name;
private String description;
}

View File

@ -1,5 +0,0 @@
@org.hibernate.annotations.Type( type = "nstring" )
private String name;
@org.hibernate.annotations.Type( type = "materialized_nclob" )
private String description;

View File

@ -1,17 +0,0 @@
public class Contact {
private Integer id;
private Name name;
private String notes;
private URL website;
private boolean starred;
// getters and setters ommitted
}
public class Name {
private String first;
private String middle;
private String last;
// getters and setters ommitted
}

View File

@ -1,9 +0,0 @@
create table Contact (
id INTEGER NOT NULL,
first_name VARCHAR,
middle_name VARCHAR,
last_name VARCHAR,
notes VARCHAR,
starred BIT,
website VARCHAR
)

View File

@ -0,0 +1,10 @@
create table Contact (
id integer not null,
first varchar(255),
last varchar(255),
middle varchar(255),
notes varchar(255),
starred boolean not null,
website varchar(255),
primary key (id)
)

View File

@ -1,6 +1,7 @@
[[mapping_types]] [[mapping-types]]
=== Mapping types === Mapping types
:sourcedir: extras :sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping
:extrasdir: extras/types
Hibernate understands both the Java and JDBC representations of application data. Hibernate understands both the Java and JDBC representations of application data.
The ability to read/write this data from/to the database is the function of a Hibernate _type_. The ability to read/write this data from/to the database is the function of a Hibernate _type_.
@ -10,7 +11,6 @@ This Hibernate type also describes various aspects of behavior of the Java type
.Usage of the word _type_ .Usage of the word _type_
[NOTE] [NOTE]
==== ====
The Hibernate type is neither a Java type nor a SQL data type. The Hibernate type is neither a Java type nor a SQL data type.
It provides information about both of these as well as understanding marshalling between. It provides information about both of these as well as understanding marshalling between.
@ -19,16 +19,17 @@ When you encounter the term type in discussions of Hibernate, it may refer to th
To help understand the type categorizations, let's look at a simple table and domain model that we wish to map. To help understand the type categorizations, let's look at a simple table and domain model that we wish to map.
[[mapping-types-basic-example]]
.Simple table and domain model .Simple table and domain model
==== ====
[source,sql] [source, SQL, indent=0]
---- ----
include::{sourcedir}/mapping_types/Contact.sql[] include::{extrasdir}/mapping-types-basic-example.sql[]
---- ----
[source,java] [source, JAVA, indent=0]
---- ----
include::{sourcedir}/mapping_types/Contact.java[] include::{sourcedir}/basic/TypeCategoryTest.java[tags=mapping-types-basic-example]
---- ----
==== ====

View File

@ -53,6 +53,8 @@ import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.SessionFactoryServiceRegistry; import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.hibernate.userguide.mapping.basic.BitSetType;
import org.hibernate.userguide.mapping.basic.BitSetUserType;
import org.junit.Test; import org.junit.Test;
@ -507,4 +509,42 @@ public class BootstrapTest {
} }
} }
//end::bootstrap-native-PersistenceUnitInfoImpl-example[] //end::bootstrap-native-PersistenceUnitInfoImpl-example[]
@Test
public void test_basic_custom_type_register_BasicType_example() {
try {
//tag::basic-custom-type-register-BasicType-example[]
ServiceRegistry standardRegistry =
new StandardServiceRegistryBuilder().build();
MetadataSources sources = new MetadataSources( standardRegistry );
MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
metadataBuilder.applyBasicType( BitSetType.INSTANCE );
//end::basic-custom-type-register-BasicType-example[]
}
catch (Exception ignore) {
}
}
@Test
public void test_basic_custom_type_register_UserType_example() {
try {
//tag::basic-custom-type-register-UserType-example[]
ServiceRegistry standardRegistry =
new StandardServiceRegistryBuilder().build();
MetadataSources sources = new MetadataSources( standardRegistry );
MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
metadataBuilder.applyBasicType( BitSetUserType.INSTANCE, "bitset" );
//end::basic-custom-type-register-UserType-example[]
}
catch (Exception ignore) {
}
}
} }

View File

@ -0,0 +1,40 @@
package org.hibernate.userguide.mapping.basic;
import java.util.BitSet;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.DiscriminatorType;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
/**
* @author Vlad Mihalcea
*/
//tag::basic-custom-type-BitSetType-example[]
public class BitSetType
extends AbstractSingleColumnStandardBasicType<BitSet>
implements DiscriminatorType<BitSet> {
public static final BitSetType INSTANCE = new BitSetType();
public BitSetType() {
super( VarcharTypeDescriptor.INSTANCE, BitSetTypeDescriptor.INSTANCE );
}
@Override
public BitSet stringToObject(String xml) throws Exception {
return fromString( xml );
}
@Override
public String objectToSQLString(BitSet value, Dialect dialect) throws Exception {
return toString( value );
}
@Override
public String getName() {
return "bitset";
}
}
//end::basic-custom-type-BitSetType-example[]

View File

@ -0,0 +1,75 @@
package org.hibernate.userguide.mapping.basic;
import java.util.BitSet;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
/**
* @author Vlad Mihalcea
*/
//tag::basic-custom-type-BitSetTypeDescriptor-example[]
public class BitSetTypeDescriptor extends AbstractTypeDescriptor<BitSet> {
private static final String DELIMITER = ",";
public static final BitSetTypeDescriptor INSTANCE = new BitSetTypeDescriptor();
public BitSetTypeDescriptor() {
super( BitSet.class );
}
@Override
public String toString(BitSet value) {
StringBuilder builder = new StringBuilder();
for ( long token : value.toLongArray() ) {
if ( builder.length() > 0 ) {
builder.append( DELIMITER );
}
builder.append( Long.toString( token, 2 ) );
}
return builder.toString();
}
@Override
public BitSet fromString(String string) {
if ( string == null || string.isEmpty() ) {
return null;
}
String[] tokens = string.split( DELIMITER );
long[] values = new long[tokens.length];
for ( int i = 0; i < tokens.length; i++ ) {
values[i] = Long.valueOf( tokens[i], 2 );
}
return BitSet.valueOf( values );
}
@SuppressWarnings({"unchecked"})
public <X> X unwrap(BitSet value, Class<X> type, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( BitSet.class.isAssignableFrom( type ) ) {
return (X) value;
}
if ( String.class.isAssignableFrom( type ) ) {
return (X) toString( value);
}
throw unknownUnwrap( type );
}
public <X> BitSet wrap(X value, WrapperOptions options) {
if ( value == null ) {
return null;
}
if ( String.class.isInstance( value ) ) {
return fromString( (String) value );
}
if ( BitSet.class.isInstance( value ) ) {
return (BitSet) value;
}
throw unknownWrap( value.getClass() );
}
}
//end::basic-custom-type-BitSetTypeDescriptor-example[]

View File

@ -0,0 +1,92 @@
/*
* 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.BitSet;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Type;
import org.hibernate.cfg.Configuration;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
public class BitSetTypeTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Product.class
};
}
@Override
protected Configuration constructAndConfigureConfiguration() {
Configuration configuration = super.constructAndConfigureConfiguration();
//tag::basic-custom-type-register-BasicType-example[]
configuration.registerTypeContributor( (typeContributions, serviceRegistry) -> {
typeContributions.contributeType( BitSetType.INSTANCE );
} );
//end::basic-custom-type-register-BasicType-example[]
return configuration;
}
@Test
public void test() {
//tag::basic-custom-type-BitSetType-persistence-example[]
BitSet bitSet = BitSet.valueOf( new long[] {1, 2, 3} );
doInHibernate( this::sessionFactory, session -> {
Product product = new Product( );
product.setId( 1 );
product.setBitSet( bitSet );
session.persist( product );
} );
doInHibernate( this::sessionFactory, session -> {
Product product = session.get( Product.class, 1 );
assertEquals(bitSet, product.getBitSet());
} );
//end::basic-custom-type-BitSetType-persistence-example[]
}
//tag::basic-custom-type-BitSetType-mapping-example[]
@Entity(name = "Product")
public static class Product {
@Id
private Integer id;
@Type( type = "bitset" )
private BitSet bitSet;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BitSet getBitSet() {
return bitSet;
}
public void setBitSet(BitSet bitSet) {
this.bitSet = bitSet;
}
}
//end::basic-custom-type-BitSetType-mapping-example[]
}

View File

@ -0,0 +1,106 @@
package org.hibernate.userguide.mapping.basic;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.BitSet;
import java.util.Objects;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StringType;
import org.hibernate.usertype.UserType;
import org.jboss.logging.Logger;
/**
* @author Vlad Mihalcea
*/
//tag::basic-custom-type-BitSetUserType-example[]
public class BitSetUserType implements UserType {
public static final BitSetUserType INSTANCE = new BitSetUserType();
private static final Logger log = Logger.getLogger( BitSetUserType.class );
@Override
public int[] sqlTypes() {
return new int[] {StringType.INSTANCE.sqlType()};
}
@Override
public Class returnedClass() {
return String.class;
}
@Override
public boolean equals(Object x, Object y)
throws HibernateException {
return Objects.equals( x, y );
}
@Override
public int hashCode(Object x)
throws HibernateException {
return Objects.hashCode( x );
}
@Override
public Object nullSafeGet(
ResultSet rs, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
String columnName = names[0];
String columnValue = (String) rs.getObject( columnName );
log.debugv("Result set column {0} value is {1}", columnName, columnValue);
return columnValue == null ? null :
BitSetTypeDescriptor.INSTANCE.fromString( columnValue );
}
@Override
public void nullSafeSet(
PreparedStatement st, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
if ( value == null ) {
log.debugv("Binding null to parameter {0} ",index);
st.setNull( index, Types.VARCHAR );
}
else {
String stringValue = BitSetTypeDescriptor.INSTANCE.toString( (BitSet) value );
log.debugv("Binding {0} to parameter {1} ", stringValue, index);
st.setString( index, stringValue );
}
}
@Override
public Object deepCopy(Object value)
throws HibernateException {
return value == null ? null :
BitSet.valueOf( BitSet.class.cast( value ).toLongArray() );
}
@Override
public boolean isMutable() {
return true;
}
@Override
public Serializable disassemble(Object value)
throws HibernateException {
return (BitSet) deepCopy( value );
}
@Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return deepCopy( cached );
}
@Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return deepCopy( original );
}
}
//end::basic-custom-type-BitSetUserType-example[]

View File

@ -0,0 +1,90 @@
/*
* 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.BitSet;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Type;
import org.hibernate.cfg.Configuration;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
public class BitSetUserTypeTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Product.class
};
}
@Override
protected Configuration constructAndConfigureConfiguration() {
Configuration configuration = super.constructAndConfigureConfiguration();
//tag::basic-custom-type-register-UserType-example[]
configuration.registerTypeContributor( (typeContributions, serviceRegistry) -> {
typeContributions.contributeType( BitSetUserType.INSTANCE, "bitset");
} );
//end::basic-custom-type-register-UserType-example[]
return configuration;
}
@Test
public void test() {
BitSet bitSet = BitSet.valueOf( new long[] {1, 2, 3} );
doInHibernate( this::sessionFactory, session -> {
Product product = new Product( );
product.setId( 1 );
product.setBitSet( bitSet );
session.persist( product );
} );
doInHibernate( this::sessionFactory, session -> {
Product product = session.get( Product.class, 1 );
assertEquals(bitSet, product.getBitSet());
} );
}
//tag::basic-custom-type-BitSetUserType-mapping-example[]
@Entity(name = "Product")
public static class Product {
@Id
private Integer id;
@Type( type = "bitset" )
private BitSet bitSet;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public BitSet getBitSet() {
return bitSet;
}
public void setBitSet(BitSet bitSet) {
this.bitSet = bitSet;
}
}
//end::basic-custom-type-BitSetUserType-mapping-example[]
}

View File

@ -0,0 +1,58 @@
/*
* 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 javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class ExplicitBasicTypeTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Product.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Product product = new Product( );
product.id = 1;
entityManager.persist( product );
} );
}
//tag::basic-annotation-explicit-example[]
@Entity(name = "Product")
public class Product {
@Id
@Basic
private Integer id;
@Basic
private String sku;
@Basic
private String name;
@Basic
private String description;
}
//end::basic-annotation-explicit-example[]
}

View File

@ -0,0 +1,55 @@
/*
* 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 javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class ExplicitColumnNamingTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Product.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Product product = new Product( );
product.id = 1;
entityManager.persist( product );
} );
}
//tag::basic-annotation-explicit-column-example[]
@Entity(name = "Product")
public class Product {
@Id
private Integer id;
private String sku;
private String name;
@Column( name = "NOTES" )
private String description;
}
//end::basic-annotation-explicit-column-example[]
}

View File

@ -0,0 +1,53 @@
/*
* 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 javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class ExplicitTypeTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Product.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
} );
}
//tag::basic-type-annotation-example[]
@Entity(name = "Product")
public class Product {
@Id
private Integer id;
private String sku;
@org.hibernate.annotations.Type( type = "nstring" )
private String name;
@org.hibernate.annotations.Type( type = "materialized_nclob" )
private String description;
}
//end::basic-type-annotation-example[]
}

View File

@ -0,0 +1,53 @@
/*
* 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 javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class ImplicitBasicTypeTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Product.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Product product = new Product( );
product.id = 1;
entityManager.persist( product );
} );
}
//tag::basic-annotation-implicit-example[]
@Entity(name = "Product")
public class Product {
@Id
private Integer id;
private String sku;
private String name;
private String description;
}
//end::basic-annotation-implicit-example[]
}

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.userguide.mapping.basic;
import java.net.URL;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class TypeCategoryTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Contact.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Contact contact = new Contact( );
contact.id = 1;
entityManager.persist( contact );
} );
}
//tag::mapping-types-basic-example[]
@Entity(name = "Contact")
public static class Contact {
@Id
private Integer id;
private Name name;
private String notes;
private URL website;
private boolean starred;
// getters and setters omitted
}
@Embeddable
public class Name {
private String first;
private String middle;
private String last;
// getters and setters omitted
}
//end::mapping-types-basic-example[]
}