HHH-10127 - Allow indication that converted types are immutable
This commit is contained in:
parent
f2ba4b9cee
commit
672d8fe4ab
|
@ -1054,6 +1054,58 @@ include::{extrasdir}/basic/basic-jpa-convert-period-string-converter-sql-example
|
|||
----
|
||||
====
|
||||
|
||||
[[basic-jpa-convert-mutability]]
|
||||
===== JPA 2.1 `AttributeConverter` Mutability Plan
|
||||
|
||||
A basic type that's converted by a JPA `AttributeConverter` is immutable if the underlying Java type is immutable
|
||||
and is mutable if the associated attribute type is mutable as well.
|
||||
|
||||
Therefore, mutability is given by the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/type/descriptor/java/JavaTypeDescriptor.html#getMutabilityPlan--[`JavaTypeDescriptor#getMutabilityPlan`]
|
||||
of the associated entity attribute type.
|
||||
|
||||
====== Immutable types
|
||||
|
||||
If the entity attribute is a `String`, a primitive wrapper (e.g. `Integer`, `Long`) an Enum type, or any other immutable `Object` type,
|
||||
then you can only change the entity attribute value by reassigning it to a new value.
|
||||
|
||||
Considering we have the same `Period` entity attribute as illustrated in the <<basic-jpa-convert>> section:
|
||||
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/converter/PeriodStringTest.java[tags=basic-jpa-convert-period-string-converter-mapping-example]
|
||||
----
|
||||
|
||||
The only way to change the `span` attribute is to reassign it to a different value:
|
||||
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/converter/PeriodStringTest.java[tags=basic-jpa-convert-period-string-converter-immutability-plan-example]
|
||||
----
|
||||
|
||||
====== Mutable types
|
||||
|
||||
On the other hand, consider the following example where the `Money` type is a mutable.
|
||||
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/converter/MoneyConverterTest.java[tags=basic-jpa-convert-money-converter-mapping-example]
|
||||
----
|
||||
|
||||
A mutable `Object` allows you to modify its internal structure, and Hibernate dirty checking mechanism is going to propagate the change to the database:
|
||||
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/converter/MoneyConverterTest.java[tags=basic-jpa-convert-money-converter-mutability-plan-example]
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Although the `AttributeConverter` types can be mutable so that dirty checking, deep copying and second-level caching work properly,
|
||||
treating these as immutable (when they really are) is more efficient.
|
||||
|
||||
For this reason, prefer immutable types over mutable ones whenever possible.
|
||||
====
|
||||
|
||||
[[mapping-quoted-identifiers]]
|
||||
==== SQL quoted identifiers
|
||||
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.userguide.mapping.converter;
|
||||
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Convert;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
|
||||
/**
|
||||
* @author Vlad Mihalcea
|
||||
*/
|
||||
public class MoneyConverterTest extends BaseEntityManagerFunctionalTestCase {
|
||||
|
||||
@Test
|
||||
public void testConverterMutability() {
|
||||
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Account account = new Account();
|
||||
account.setId( 1L );
|
||||
account.setOwner( "John Doe" );
|
||||
account.setBalance( new Money( 250 * 100L ) );
|
||||
|
||||
entityManager.persist( account );
|
||||
} );
|
||||
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
//tag::basic-jpa-convert-money-converter-mutability-plan-example[]
|
||||
Account account = entityManager.find( Account.class, 1L );
|
||||
account.getBalance().setCents( 150 * 100L );
|
||||
entityManager.persist( account );
|
||||
//end::basic-jpa-convert-money-converter-mutability-plan-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] { Account.class };
|
||||
}
|
||||
|
||||
//tag::basic-jpa-convert-money-converter-mapping-example[]
|
||||
public static class Money {
|
||||
|
||||
private long cents;
|
||||
|
||||
public Money(long cents) {
|
||||
this.cents = cents;
|
||||
}
|
||||
|
||||
public long getCents() {
|
||||
return cents;
|
||||
}
|
||||
|
||||
public void setCents(long cents) {
|
||||
this.cents = cents;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MoneyConverter
|
||||
implements AttributeConverter<Money, Long> {
|
||||
|
||||
@Override
|
||||
public Long convertToDatabaseColumn(Money attribute) {
|
||||
return attribute == null ? null : attribute.getCents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Money convertToEntityAttribute(Long dbData) {
|
||||
return dbData == null ? null : new Money( dbData );
|
||||
}
|
||||
}
|
||||
|
||||
//tag::basic-jpa-convert-money-converter-mapping-example[]
|
||||
@Entity(name = "Account")
|
||||
public static class Account {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String owner;
|
||||
|
||||
@Convert(converter = MoneyConverter.class)
|
||||
private Money balance;
|
||||
|
||||
//Getters and setters are omitted for brevity
|
||||
|
||||
//end::basic-jpa-convert-money-converter-mapping-example[]
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(String owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public Money getBalance() {
|
||||
return balance;
|
||||
}
|
||||
|
||||
public void setBalance(Money balance) {
|
||||
this.balance = balance;
|
||||
}
|
||||
//tag::basic-jpa-convert-money-converter-mapping-example[]
|
||||
}
|
||||
//end::basic-jpa-convert-money-converter-mapping-example[]
|
||||
}
|
|
@ -25,58 +25,73 @@ import static org.junit.Assert.assertEquals;
|
|||
*/
|
||||
public class PeriodStringTest extends BaseEntityManagerFunctionalTestCase {
|
||||
|
||||
private Period period = Period.ofYears( 1 ).plusMonths( 2 ).plusDays( 3 );
|
||||
private Period period = Period.ofYears( 1 ).plusMonths( 2 ).plusDays( 3 );
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] {
|
||||
Event.class
|
||||
};
|
||||
}
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] {
|
||||
Event.class
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLifecycle() {
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Event event = new Event( period );
|
||||
entityManager.persist( event );
|
||||
} );
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Event event = entityManager.createQuery( "from Event", Event.class ).getSingleResult();
|
||||
assertEquals( period, event.getSpan() );
|
||||
} );
|
||||
}
|
||||
@Test
|
||||
public void testLifecycle() {
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Event event = new Event( period );
|
||||
entityManager.persist( event );
|
||||
} );
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Event event = entityManager.createQuery( "from Event", Event.class ).getSingleResult();
|
||||
assertEquals( period, event.getSpan() );
|
||||
} );
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
//tag::basic-jpa-convert-period-string-converter-immutability-plan-example[]
|
||||
Event event = entityManager.createQuery( "from Event", Event.class ).getSingleResult();
|
||||
event.setSpan(Period
|
||||
.ofYears( 3 )
|
||||
.plusMonths( 2 )
|
||||
.plusDays( 1 )
|
||||
);
|
||||
//end::basic-jpa-convert-period-string-converter-immutability-plan-example[]
|
||||
} );
|
||||
}
|
||||
|
||||
//tag::basic-jpa-convert-period-string-converter-mapping-example[]
|
||||
@Entity(name = "Event")
|
||||
public static class Event {
|
||||
//tag::basic-jpa-convert-period-string-converter-mapping-example[]
|
||||
@Entity(name = "Event")
|
||||
public static class Event {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Convert(converter = PeriodStringConverter.class)
|
||||
@Column(columnDefinition = "")
|
||||
private Period span;
|
||||
@Convert(converter = PeriodStringConverter.class)
|
||||
@Column(columnDefinition = "")
|
||||
private Period span;
|
||||
|
||||
//Getters and setters are omitted for brevity
|
||||
//Getters and setters are omitted for brevity
|
||||
|
||||
//end::basic-jpa-convert-period-string-converter-mapping-example[]
|
||||
//end::basic-jpa-convert-period-string-converter-mapping-example[]
|
||||
|
||||
public Event() {
|
||||
}
|
||||
public Event() {
|
||||
}
|
||||
|
||||
public Event(Period span) {
|
||||
this.span = span;
|
||||
}
|
||||
public Event(Period span) {
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Period getSpan() {
|
||||
return span;
|
||||
}
|
||||
//tag::basic-jpa-convert-period-string-converter-mapping-example[]
|
||||
}
|
||||
//end::basic-jpa-convert-period-string-converter-mapping-example[]
|
||||
public Period getSpan() {
|
||||
return span;
|
||||
}
|
||||
|
||||
public void setSpan(Period span) {
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
//tag::basic-jpa-convert-period-string-converter-mapping-example[]
|
||||
}
|
||||
//end::basic-jpa-convert-period-string-converter-mapping-example[]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue