diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc index 47265e114c..2e5d81bacb 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/basic_types.adoc @@ -1015,20 +1015,27 @@ You can force Hibernate to quote an identifier in the generated SQL by enclosing Hibernate will use the correct quotation style for the SQL `Dialect`. This is usually double quotes, but the SQL Server uses brackets and MySQL uses backticks. +[[basic-quoting-example]] .Quoting column names ==== [source, JAVA, indent=0] ---- -@Entity @Table(name="`Line Item`") -class LineItem { +include::{sourcedir}/basic/QuotingTest.java[tags=basic-quoting-example] +---- +==== - @Id - @Column(name="`Item Id`") - private Integer id; +Because `name` and `number` are reserved words, the `Product` entity mapping uses backtricks to quote these column names. - @Column(name="`Item #`") - private Integer itemNumber -} +When saving teh following `Product entity`, Hibernate generates the following SQL insert statement: + +[[basic-quoting-persistence-example]] +.Persisting a quoted column name +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/QuotingTest.java[tags=basic-quoting-persistence-example, indent=0] + +include::{extrasdir}/basic/basic-quoting-persistence-example.sql[indent=0] ---- ==== @@ -1115,25 +1122,14 @@ As you can see, the `new Date()` object value was used for assigning the `timest ==== Column transformers: read and write expressions Hibernate allows you to customize the SQL it uses to read and write the values of columns mapped to `@Basic` types. -For example, if your database provides a set of data encryption functions, you can invoke them for individual columns like this: +For example, if your database provides a set of data encryption functions, you can invoke them for individual columns like in the following example. +[[mapping-column-read-and-write-example]] .`@ColumnTransformer` example ==== [source, JAVA, indent=0] ---- -@Entity -class CreditCard { - - @Id - private Integer id; - - @Column(name="credit_card_num") - @ColumnTransformer( - read="decrypt(credit_card_num)", - write="encrypt(?)" - ) - private String creditCardNumber; -} +include::{sourcedir}/../fetching/FetchingTest.java[tags=mapping-column-read-and-write-example] ---- ==== @@ -1144,28 +1140,12 @@ You can use the plural form `@ColumnTransformers` if more than one columns need If a property uses more than one column, you must use the `forColumn` attribute to specify which column, the expressions are targeting. +[[mapping-column-read-and-write-composite-type-example]] .`@ColumnTransformer` `forColumn` attribute usage ==== [source, JAVA, indent=0] ---- -@Entity -class User { - - @Id - private Integer id; - - @Type(type="com.acme.type.CreditCardType") - @Columns( { - @Column(name="credit_card_num"), - @Column(name="exp_date") - }) - @ColumnTransformer( - forColumn="credit_card_num", - read="decrypt(credit_card_num)", - write="encrypt(?)" - ) - private CreditCard creditCard; -} +include::{sourcedir}/basic/ColumnTransformerTest.java[tags=mapping-column-read-and-write-composite-type-example] ---- ==== @@ -1177,18 +1157,48 @@ This functionality is similar to a derived-property <> w The `write` expression, if specified, must contain exactly one '?' placeholder for the value. +[[mapping-column-read-and-write-composite-type-persistence-example]] +.Persisting an entity with a `@ColumnTransformer` and a composite type +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/ColumnTransformerTest.java[tags=mapping-column-read-and-write-composite-type-persistence-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-column-read-and-write-composite-type-persistence-example.sql[] +---- +==== + [[mapping-column-formula]] ==== Formula Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment) +[[mapping-column-formula-example]] .`@Formula` mapping usage ==== [source, JAVA, indent=0] ---- -@Formula("obj_length * obj_height * obj_width") -private long objectVolume; +include::{sourcedir}/basic/FormulaTest.java[tags=mapping-column-formula-example] +---- +==== + +When loading the `Account` entity, Hibernate is going to calculate the `interest` property using the configured `@Formula`: + +[[mapping-column-formula-persistence-example]] +.Persisting an entity with a `@Formula` mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/FormulaTest.java[tags=mapping-column-formula-persistence-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-column-formula-persistence-example.sql[] ---- ==== @@ -1198,7 +1208,7 @@ The SQL fragment can be as complex as you want and even include subselects. ==== [[mapping-column-any]] -==== Any +==== @Any mapping There is one more type of property mapping. The `@Any` mapping defines a polymorphic association to classes from multiple tables. @@ -1215,54 +1225,116 @@ This is not the usual way of mapping polymorphic associations and you should use The `@Any` annotation describes the column holding the metadata information. To link the value of the metadata information and an actual entity type, the `@AnyDef` and `@AnyDefs` annotations are used. The `metaType` attribute allows the application to specify a custom type that maps database column values to persistent classes that have identifier properties of the type specified by `idType`. -You must specify the mapping from values of the metaType to class names. +You must specify the mapping from values of the `metaType` to class names. +For the next examples, consider the following `Property` class hierarchy: + +[[mapping-column-any-property-example]] +.`Property` class hierarchy +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/any/Property.java[tags=mapping-column-any-property-example] + +include::{sourcedir}/basic/any/IntegerProperty.java[tags=mapping-column-any-property-example] + +include::{sourcedir}/basic/any/StringProperty.java[tags=mapping-column-any-property-example] +---- +==== + +A `PropertyHolder` can reference any such property, and, because each `Property` belongs to a separate table, the `@Any` annotation is, therefore, required. + +[[mapping-column-any-example]] .`@Any` mapping usage ==== [source, JAVA, indent=0] ---- -@Any( - metaColumn = @Column( name = "property_type" ), - fetch=FetchType.EAGER -) -@AnyMetaDef( - idType = "integer", - metaType = "string", - metaValues = { - @MetaValue( value = "S", targetEntity = StringProperty.class ), - @MetaValue( value = "I", targetEntity = IntegerProperty.class ) - } -) -@JoinColumn( name = "property_id" ) -private Property mainProperty; +include::{sourcedir}/basic/any/PropertyHolder.java[tags=mapping-column-any-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-column-any-example.sql[] ---- ==== -Note that `@AnyDef` can be mutualized and reused. It is recommended to place it as a package metadata in this case. +As you can see, there are two columns used to reference a `Property` instance: `property_id` and `property_type`. +The `property_id` is used to match the `id` column of either the `string_property` or `integer_property` tables, +while the `property_type` is used to match the `string_property` or the `integer_property` table. -.`@AnyMetaDef` mapping usage +The table resolving mapping is defined by the `metaDef` attribute which references an `@AnyMetaDef` mapping. +Although the `@AnyMetaDef` mapping could be set right next to the `@Any` annotation, +it's good practice to reuse it, therefore it makes sense to configure it on a class or package-level basis. + +The `package-info.java` contains the `@AnyMetaDef` mapping: + +[[mapping-column-any-meta-def-example]] +.`@Any` mapping usage ==== [source, JAVA, indent=0] ---- -//on a package -@AnyMetaDef( name="property" - idType = "integer", - metaType = "string", - metaValues = { - @MetaValue( value = "S", targetEntity = StringProperty.class ), - @MetaValue( value = "I", targetEntity = IntegerProperty.class ) - } -) -package org.hibernate.test.annotations.any; - -//in a class -@Any( - metaDef="property", - metaColumn = @Column( name = "property_type" ), - fetch=FetchType.EAGER -) -@JoinColumn( name = "property_id" ) -private Property mainProperty; +include::{sourcedir}/basic/any/package-info.java[tags=mapping-column-any-meta-def-example] +---- +==== + +[NOTE] +==== +It is recommended to place the `@AnyMetaDef` mapping as a package metadata. +==== + +To see how the `@Any` annotation in action, consider the following example: + +[[mapping-column-any-persistence-example]] +.`@Any` mapping usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/any/AnyTest.java[tags=mapping-column-any-persistence-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-column-any-persistence-example.sql[] +---- +==== + +[[mapping-column-many-to-any]] +===== `@ManyToAny` mapping + +The `@Any` mapping is useful to emulate a `@ManyToOne` association when there can be multiple target entities. +To emulate a `@OneToMany` association, the `@ManyToAny` annotation must be used. + +In the following example, the `PropertyRepository` entity has a collection of `Property` entities. +The `repository_properties` link table holds the associations between `PropertyRepository` and `Property` entities. + +[[mapping-column-many-to-any-example]] +.`@ManyToAny` mapping usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/any/PropertyRepository.java[tags=mapping-column-many-to-any-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-column-many-to-any-example.sql[] +---- +==== + + +To see how the `@ManyToAny` annotation works, consider the following example: + +[[mapping-column-many-to-any-persistence-example]] +.`@Any` mapping usage +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/basic/any/ManyToAnyTest.java[tags=mapping-column-many-to-any-persistence-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/basic/mapping-column-many-to-any-persistence-example.sql[] ---- ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/basic-quoting-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/basic-quoting-persistence-example.sql new file mode 100644 index 0000000000..13dd6d2f85 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/basic-quoting-persistence-example.sql @@ -0,0 +1,2 @@ +INSERT INTO Product ("name", "number", id) +VALUES ('Mobile phone', '123-456-7890', 1) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-example.sql new file mode 100644 index 0000000000..f25ee01e50 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-example.sql @@ -0,0 +1,6 @@ +CREATE TABLE property_holder ( + id BIGINT NOT NULL, + property_type VARCHAR(255), + property_id BIGINT, + PRIMARY KEY ( id ) +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persistence-example.sql new file mode 100644 index 0000000000..ca39900bc8 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-any-persistence-example.sql @@ -0,0 +1,25 @@ +INSERT INTO integer_property + ( "name", "value", id ) +VALUES ( 'age', 23, 1 ) + +INSERT INTO string_property + ( "name", "value", id ) +VALUES ( 'name', 'John Doe', 1 ) + +INSERT INTO property_holder + ( property_type, property_id, id ) +VALUES ( 'S', 1, 1 ) + + +SELECT ph.id AS id1_1_0_, + ph.property_type AS property2_1_0_, + ph.property_id AS property3_1_0_ +FROM property_holder ph +WHERE ph.id = 1 + + +SELECT sp.id AS id1_2_0_, + sp."name" AS name2_2_0_, + sp."value" AS value3_2_0_ +FROM string_property sp +WHERE sp.id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-formula-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-formula-persistence-example.sql new file mode 100644 index 0000000000..4106a98ae3 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-formula-persistence-example.sql @@ -0,0 +1,12 @@ +INSERT INTO Account (credit, rate, id) +VALUES (5000.0, 0.0125, 1) + +SELECT + a.id as id1_0_0_, + a.credit as credit2_0_0_, + a.rate as rate3_0_0_, + a.credit * a.rate as formula0_0_ +FROM + Account a +WHERE + a.id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-example.sql new file mode 100644 index 0000000000..08c8171611 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-example.sql @@ -0,0 +1,10 @@ +CREATE TABLE property_repository ( + id BIGINT NOT NULL, + PRIMARY KEY ( id ) +) + +CREATE TABLE repository_properties ( + repository_id BIGINT NOT NULL, + property_type VARCHAR(255), + property_id BIGINT NOT NULL +) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persistence-example.sql new file mode 100644 index 0000000000..392de28471 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-many-to-any-persistence-example.sql @@ -0,0 +1,36 @@ +INSERT INTO integer_property + ( "name", "value", id ) +VALUES ( 'age', 23, 1 ) + +INSERT INTO string_property + ( "name", "value", id ) +VALUES ( 'name', 'John Doe', 1 ) + +INSERT INTO property_repository ( id ) +VALUES ( 1 ) + +INSERT INTO repository_properties + ( repository_id , property_type , property_id ) +VALUES + ( 1 , 'I' , 1 ) + +INSERT INTO repository_properties + ( repository_id , property_type , property_id ) +VALUES + ( 1 , 'S' , 1 ) + +SELECT pr.id AS id1_1_0_ +FROM property_repository pr +WHERE pr.id = 1 + +SELECT ip.id AS id1_0_0_ , + integerpro0_."name" AS name2_0_0_ , + integerpro0_."value" AS value3_0_0_ +FROM integer_property integerpro0_ +WHERE integerpro0_.id = 1 + +SELECT sp.id AS id1_3_0_ , + sp."name" AS name2_3_0_ , + sp."value" AS value3_3_0_ +FROM string_property sp +WHERE sp.id = 1 diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-read-and-write-composite-type-persistence-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-read-and-write-composite-type-persistence-example.sql new file mode 100644 index 0000000000..f2253f14f0 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/basic/mapping-column-read-and-write-composite-type-persistence-example.sql @@ -0,0 +1,11 @@ +INSERT INTO Savings (money, currency, id) +VALUES (10 * 100, 'USD', 1) + +SELECT + s.id as id1_0_0_, + s.money / 100 as money2_0_0_, + s.currency as currency3_0_0_ +FROM + Savings s +WHERE + s.id = 1 \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java index 464b424d4f..e3cec12a36 100644 --- a/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/fetching/FetchingTest.java @@ -168,6 +168,7 @@ public class FetchingTest extends BaseEntityManagerFunctionalTestCase { //Getters and setters omitted for brevity } + //tag::mapping-column-read-and-write-example[] @Entity(name = "Employee") public static class Employee { @@ -194,6 +195,7 @@ public class FetchingTest extends BaseEntityManagerFunctionalTestCase { //Getters and setters omitted for brevity } + //end::mapping-column-read-and-write-example[] @Entity(name = "Project") public class Project { diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ColumnTransformerTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ColumnTransformerTest.java new file mode 100644 index 0000000000..2cc03a5553 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/ColumnTransformerTest.java @@ -0,0 +1,101 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic; + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.Locale; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.ColumnTransformer; +import org.hibernate.annotations.Columns; +import org.hibernate.annotations.Type; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.RequiresDialect; +import org.junit.Test; + +import static org.hibernate.userguide.util.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(H2Dialect.class) +public class ColumnTransformerTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Savings.class + }; + } + + @Test + public void testLifecycle() { + //tag::mapping-column-read-and-write-composite-type-persistence-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::basic-datetime-temporal-date-persist-example[] + Savings savings = new Savings( ); + savings.setId( 1L ); + savings.setWallet( new MonetaryAmount( BigDecimal.TEN, Currency.getInstance( Locale.US ) ) ); + entityManager.persist( savings ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Savings savings = entityManager.find( Savings.class, 1L ); + assertEquals( 10, savings.getWallet().getAmount().intValue()); + } ); + //end::mapping-column-read-and-write-composite-type-persistence-example[] + } + + //tag::mapping-column-read-and-write-composite-type-example[] + @Entity(name = "Savings") + public static class Savings { + + @Id + private Long id; + + @Type(type = "org.hibernate.userguide.mapping.basic.MonetaryAmountUserType") + @Columns(columns = { + @Column(name = "money"), + @Column(name = "currency") + }) + @ColumnTransformer( + forColumn = "money", + read = "money / 100", + write = "? * 100" + ) + private MonetaryAmount wallet; + + //Getters and setters omitted for brevity + + //end::mapping-column-read-and-write-composite-type-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public MonetaryAmount getWallet() { + return wallet; + } + + public void setWallet(MonetaryAmount wallet) { + this.wallet = wallet; + } + + + //tag::mapping-column-read-and-write-composite-type-example[] + } + //end::mapping-column-read-and-write-composite-type-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FormulaTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FormulaTest.java new file mode 100644 index 0000000000..97ed3b2a10 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/FormulaTest.java @@ -0,0 +1,103 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.Formula; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.userguide.util.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +public class FormulaTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Account.class + }; + } + + @Test + public void testLifecycle() { + //tag::mapping-column-formula-persistence-example[] + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::basic-datetime-temporal-date-persist-example[] + Account account = new Account( ); + account.setId( 1L ); + account.setCredit( 5000d ); + account.setRate( 1.25 / 100 ); + entityManager.persist( account ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + Account account = entityManager.find( Account.class, 1L ); + assertEquals( Double.valueOf( 62.5d ), account.getInterest()); + } ); + //end::mapping-column-formula-persistence-example[] + } + + //tag::mapping-column-formula-example[] + @Entity(name = "Account") + public static class Account { + + @Id + private Long id; + + private Double credit; + + private Double rate; + + @Formula(value = "credit * rate") + private Double interest; + + //Getters and setters omitted for brevity + + //end::mapping-column-formula-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Double getCredit() { + return credit; + } + + public void setCredit(Double credit) { + this.credit = credit; + } + + public Double getRate() { + return rate; + } + + public void setRate(Double rate) { + this.rate = rate; + } + + public Double getInterest() { + return interest; + } + + public void setInterest(Double interest) { + this.interest = interest; + } + + //tag::mapping-column-formula-example[] + } + //end::mapping-column-formula-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/MonetaryAmount.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/MonetaryAmount.java new file mode 100644 index 0000000000..78328fbc11 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/MonetaryAmount.java @@ -0,0 +1,42 @@ +/* + * 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 . + */ + +//$Id$ +package org.hibernate.userguide.mapping.basic; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Currency; + +/** + * @author Emmanuel Bernard + */ +public class MonetaryAmount implements Serializable { + + private BigDecimal amount; + private Currency currency; + + public MonetaryAmount(BigDecimal amount, Currency currency) { + this.amount = amount; + this.currency = currency; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public Currency getCurrency() { + return currency; + } + + public void setCurrency(Currency currency) { + this.currency = currency; + } +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/MonetaryAmountUserType.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/MonetaryAmountUserType.java new file mode 100644 index 0000000000..ae2bf6819c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/MonetaryAmountUserType.java @@ -0,0 +1,113 @@ +/* + * 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 . + */ + +//$Id$ +package org.hibernate.userguide.mapping.basic; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Currency; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.Type; +import org.hibernate.usertype.CompositeUserType; + +/** + * @author Emmanuel Bernard + */ +public class MonetaryAmountUserType implements CompositeUserType { + + public String[] getPropertyNames() { + return new String[]{"amount", "currency"}; + } + + public Type[] getPropertyTypes() { + return new Type[]{ StandardBasicTypes.BIG_DECIMAL, StandardBasicTypes.CURRENCY }; + } + + public Object getPropertyValue(Object component, int property) throws HibernateException { + MonetaryAmount ma = (MonetaryAmount) component; + return property == 0 ? ma.getAmount() : ma.getCurrency(); + } + + public void setPropertyValue(Object component, int property, Object value) + throws HibernateException { + MonetaryAmount ma = (MonetaryAmount) component; + if ( property == 0 ) { + ma.setAmount( (BigDecimal) value ); + } + else { + ma.setCurrency( (Currency) value ); + } + } + + public Class returnedClass() { + return String.class; + } + + public boolean equals(Object x, Object y) throws HibernateException { + if ( x == y ) return true; + if ( x == null || y == null ) return false; + MonetaryAmount mx = (MonetaryAmount) x; + MonetaryAmount my = (MonetaryAmount) y; + return mx.getAmount().equals( my.getAmount() ) && + mx.getCurrency().equals( my.getCurrency() ); + } + + public int hashCode(Object x) throws HibernateException { + return ( (MonetaryAmount) x ).getAmount().hashCode(); + } + + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + BigDecimal amt = StandardBasicTypes.BIG_DECIMAL.nullSafeGet( rs, names[0], session); + Currency cur = StandardBasicTypes.CURRENCY.nullSafeGet( rs, names[1], session ); + if ( amt == null ) return null; + return new MonetaryAmount( amt, cur ); + } + + public void nullSafeSet( + PreparedStatement st, Object value, int index, + SessionImplementor session + ) throws HibernateException, SQLException { + MonetaryAmount ma = (MonetaryAmount) value; + BigDecimal amt = ma == null ? null : ma.getAmount(); + Currency cur = ma == null ? null : ma.getCurrency(); + StandardBasicTypes.BIG_DECIMAL.nullSafeSet( st, amt, index, session ); + StandardBasicTypes.CURRENCY.nullSafeSet( st, cur, index + 1, session ); + } + + public Object deepCopy(Object value) throws HibernateException { + MonetaryAmount ma = (MonetaryAmount) value; + return new MonetaryAmount( ma.getAmount(), ma.getCurrency() ); + } + + public boolean isMutable() { + return true; + } + + public Serializable disassemble(Object value, SessionImplementor session) + throws HibernateException { + return (Serializable) deepCopy( value ); + } + + public Object assemble(Serializable cached, SessionImplementor session, Object owner) + throws HibernateException { + return deepCopy( cached ); + } + + public Object replace(Object original, Object target, SessionImplementor session, Object owner) + throws HibernateException { + return deepCopy( original ); //TODO: improve + } + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/QuotingTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/QuotingTest.java new file mode 100644 index 0000000000..40ee08bec4 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/QuotingTest.java @@ -0,0 +1,89 @@ +/* + * 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 . + */ +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 QuotingTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::basic-quoting-persistence-example[] + Product product = new Product(); + product.setId( 1L ); + product.setName( "Mobile phone" ); + product.setNumber( "123-456-7890" ); + entityManager.persist( product ); + //end::basic-quoting-persistence-example[] + } ); + } + + //tag::basic-quoting-example[] + @Entity(name = "Product") + public static class Product { + + @Id + private Long id; + + @Column(name = "`name`") + private String name; + + @Column(name = "`number`") + private String number; + + //Getters and setters are omitted for brevity + + //end::basic-quoting-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + + //tag::basic-quoting-example[] + } + //end::basic-quoting-example[] +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/AnyTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/AnyTest.java new file mode 100644 index 0000000000..f4154e412c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/AnyTest.java @@ -0,0 +1,69 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic.any; + +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 AnyTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + IntegerProperty.class, + StringProperty.class, + PropertyHolder.class + }; + } + + @Override + protected String[] getAnnotatedPackages() { + return new String[] { + getClass().getPackage().getName() + }; + } + + @Test + public void test() { + + //tag::mapping-column-any-persistence-example[] + doInHibernate( this::sessionFactory, session -> { + IntegerProperty ageProperty = new IntegerProperty(); + ageProperty.setId( 1L ); + ageProperty.setName( "age" ); + ageProperty.setValue( 23 ); + + StringProperty nameProperty = new StringProperty(); + nameProperty.setId( 1L ); + nameProperty.setName( "name" ); + nameProperty.setValue( "John Doe" ); + + session.persist( ageProperty ); + session.persist( nameProperty ); + + PropertyHolder namePropertyHolder = new PropertyHolder(); + namePropertyHolder.setId( 1L ); + namePropertyHolder.setProperty( nameProperty ); + session.persist( namePropertyHolder ); + } ); + + doInHibernate( this::sessionFactory, session -> { + PropertyHolder propertyHolder = session.get( PropertyHolder.class, 1L ); + assertEquals("name", propertyHolder.getProperty().getName()); + assertEquals("John Doe", propertyHolder.getProperty().getValue()); + } ); + //end::mapping-column-any-persistence-example[] + } + + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/IntegerProperty.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/IntegerProperty.java new file mode 100644 index 0000000000..a0b588aad1 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/IntegerProperty.java @@ -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 . + */ +package org.hibernate.userguide.mapping.basic.any; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +//tag::mapping-column-any-property-example[] + +@Entity +@Table(name="integer_property") +public class IntegerProperty implements Property { + + @Id + private Long id; + + @Column(name = "`name`") + private String name; + + @Column(name = "`value`") + private Integer value; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getValue() { + return value; + } + + public void setValue(Integer value) { + this.value = value; + } +} +//end::mapping-column-any-property-example[] + diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/ManyToAnyTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/ManyToAnyTest.java new file mode 100644 index 0000000000..71c831ae33 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/ManyToAnyTest.java @@ -0,0 +1,73 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic.any; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.userguide.util.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + */ +public class ManyToAnyTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + IntegerProperty.class, + StringProperty.class, + PropertyRepository.class + }; + } + + @Override + protected String[] getAnnotatedPackages() { + return new String[] { + getClass().getPackage().getName() + }; + } + + @Test + public void test() { + + //tag::mapping-column-many-to-any-persistence-example[] + doInHibernate( this::sessionFactory, session -> { + IntegerProperty ageProperty = new IntegerProperty(); + ageProperty.setId( 1L ); + ageProperty.setName( "age" ); + ageProperty.setValue( 23 ); + + StringProperty nameProperty = new StringProperty(); + nameProperty.setId( 1L ); + nameProperty.setName( "name" ); + nameProperty.setValue( "John Doe" ); + + session.persist( ageProperty ); + session.persist( nameProperty ); + + PropertyRepository propertyRepository = new PropertyRepository(); + propertyRepository.setId( 1L ); + propertyRepository.getProperties().add( ageProperty ); + propertyRepository.getProperties().add( nameProperty ); + session.persist( propertyRepository ); + } ); + + doInHibernate( this::sessionFactory, session -> { + PropertyRepository propertyRepository = session.get( PropertyRepository.class, 1L ); + assertEquals(2, propertyRepository.getProperties().size()); + for(Property property : propertyRepository.getProperties()) { + assertNotNull( property.getValue() ); + } + } ); + //end::mapping-column-many-to-any-persistence-example[] + } + + +} diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/Property.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/Property.java new file mode 100644 index 0000000000..f2c2df7b3e --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/Property.java @@ -0,0 +1,16 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic.any; + +//tag::mapping-column-any-property-example[] +public interface Property { + + String getName(); + + T getValue(); +} +//end::mapping-column-any-property-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyHolder.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyHolder.java new file mode 100644 index 0000000000..fc80602480 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyHolder.java @@ -0,0 +1,52 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic.any; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; + +import org.hibernate.annotations.Any; + +//tag::mapping-column-any-example[] +@Entity +@Table( name = "property_holder" ) +public class PropertyHolder { + + @Id + private Long id; + + @Any( + metaDef = "PropertyMetaDef", + metaColumn = @Column( name = "property_type" ) + ) + @JoinColumn( name = "property_id" ) + private Property property; + + //Getters and setters are omitted for brevity + +//end::mapping-column-any-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Property getProperty() { + return property; + } + + public void setProperty(Property property) { + this.property = property; + } +//tag::mapping-column-any-example[] +} +//end::mapping-column-any-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyRepository.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyRepository.java new file mode 100644 index 0000000000..19119c6058 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/PropertyRepository.java @@ -0,0 +1,57 @@ +/* + * 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 . + */ +package org.hibernate.userguide.mapping.basic.any; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.Table; + +import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.ManyToAny; + +//tag::mapping-column-many-to-any-example[] +@Entity +@Table( name = "property_repository" ) +public class PropertyRepository { + + @Id + private Long id; + + @ManyToAny( + metaDef = "PropertyMetaDef", + metaColumn = @Column( name = "property_type" ) + ) + @Cascade( { org.hibernate.annotations.CascadeType.ALL }) + @JoinTable(name = "repository_properties", + joinColumns = @JoinColumn(name = "repository_id"), + inverseJoinColumns = @JoinColumn(name = "property_id") + ) + private List> properties = new ArrayList<>( ); + + //Getters and setters are omitted for brevity + +//end::mapping-column-many-to-any-example[] + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List> getProperties() { + return properties; + } + + //tag::mapping-column-many-to-any-example[] +} +//end::mapping-column-many-to-any-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/StringProperty.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/StringProperty.java new file mode 100644 index 0000000000..8449ac807e --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/StringProperty.java @@ -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 . + */ +package org.hibernate.userguide.mapping.basic.any; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +//tag::mapping-column-any-property-example[] + +@Entity +@Table(name="string_property") +public class StringProperty implements Property { + + @Id + private Long id; + + @Column(name = "`name`") + private String name; + + @Column(name = "`value`") + private String value; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} +//end::mapping-column-any-property-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/package-info.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/package-info.java new file mode 100644 index 0000000000..0f75dd51b8 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/any/package-info.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +// $Id$ +//tag::mapping-column-any-meta-def-example[] +@AnyMetaDefs( + @AnyMetaDef( name= "PropertyMetaDef", metaType = "string", idType = "long", + metaValues = { + @MetaValue(value = "S", targetEntity = StringProperty.class), + @MetaValue(value = "I", targetEntity = IntegerProperty.class) + } + ) +) +package org.hibernate.userguide.mapping.basic.any; + +import org.hibernate.annotations.AnyMetaDef; +import org.hibernate.annotations.AnyMetaDefs; +import org.hibernate.annotations.MetaValue; +//end::mapping-column-any-meta-def-example[] + diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/any/package-info.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/package-info.java index df4b7be428..006c534d7d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/any/package-info.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/any/package-info.java @@ -4,16 +4,22 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ - -// $Id$ @AnyMetaDefs( - @AnyMetaDef( name= "Property", metaType = "string", idType = "integer", - metaValues = { - @MetaValue(value = "C", targetEntity = CharProperty.class), - @MetaValue(value = "I", targetEntity = IntegerProperty.class), - @MetaValue(value = "S", targetEntity = StringProperty.class), - @MetaValue(value = "L", targetEntity = LongProperty.class) - }) + @AnyMetaDef( + name = "PropertyMetaDef", + idType = "integer", + metaType = "string", + metaValues = { + @MetaValue( + value = "S", + targetEntity = StringProperty.class + ), + @MetaValue( + value = "I", + targetEntity = IntegerProperty.class + ) + } + ) ) package org.hibernate.test.annotations.any;