Skip to content

Commit 4a19270

Browse files
committed
HHH-10111 - AttributeConverter based attributes are not marked for update when their state is modified
(cherry picked from commit bb0b604)
1 parent 1ae495b commit 4a19270

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.type.descriptor.converter;
8+
9+
import javax.persistence.AttributeConverter;
10+
11+
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
12+
13+
/**
14+
* For now we need to treat attributes to which a converter has been applied as mutable.
15+
* See Jira HHH-10111 for details.
16+
*
17+
* @author Steve Ebersole
18+
*/
19+
public class AttributeConverterMutabilityPlanImpl<T> extends MutableMutabilityPlan<T> {
20+
private final AttributeConverter attributeConverter;
21+
22+
public AttributeConverterMutabilityPlanImpl(AttributeConverter attributeConverter) {
23+
this.attributeConverter = attributeConverter;
24+
}
25+
26+
@Override
27+
@SuppressWarnings("unchecked")
28+
protected T deepCopyNotNull(T value) {
29+
return (T) attributeConverter.convertToEntityAttribute( attributeConverter.convertToDatabaseColumn( value ) );
30+
}
31+
}

hibernate-core/src/main/java/org/hibernate/type/descriptor/converter/AttributeConverterTypeAdapter.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
1212
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
13+
import org.hibernate.type.descriptor.java.MutabilityPlan;
1314
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
1415

1516
import org.jboss.logging.Logger;
@@ -31,6 +32,8 @@ public class AttributeConverterTypeAdapter<T> extends AbstractSingleColumnStanda
3132
private final Class jdbcType;
3233
private final AttributeConverter<? extends T,?> attributeConverter;
3334

35+
private final AttributeConverterMutabilityPlanImpl<T> mutabilityPlan;
36+
3437
public AttributeConverterTypeAdapter(
3538
String name,
3639
String description,
@@ -46,6 +49,8 @@ public AttributeConverterTypeAdapter(
4649
this.jdbcType = jdbcType;
4750
this.attributeConverter = attributeConverter;
4851

52+
this.mutabilityPlan = new AttributeConverterMutabilityPlanImpl<T>( attributeConverter );
53+
4954
log.debug( "Created AttributeConverterTypeAdapter -> " + name );
5055
}
5156

@@ -66,6 +71,11 @@ public AttributeConverter<? extends T,?> getAttributeConverter() {
6671
return attributeConverter;
6772
}
6873

74+
@Override
75+
protected MutabilityPlan<T> getMutabilityPlan() {
76+
return mutabilityPlan;
77+
}
78+
6979
@Override
7080
public String toString() {
7181
return description;
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.test.type.converter;
8+
9+
import javax.persistence.AttributeConverter;
10+
import javax.persistence.Column;
11+
import javax.persistence.Convert;
12+
import javax.persistence.Entity;
13+
import javax.persistence.Id;
14+
15+
import org.hibernate.Session;
16+
17+
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
18+
import org.junit.Test;
19+
20+
import static org.junit.Assert.assertEquals;
21+
22+
/**
23+
* @author Steve Ebersole
24+
*/
25+
public class DirtyCheckingTest extends BaseNonConfigCoreFunctionalTestCase {
26+
27+
@Test
28+
public void dirtyCheckAgainstNewNameInstance() {
29+
SomeEntity simpleEntity = new SomeEntity();
30+
simpleEntity.setId( 1L );
31+
simpleEntity.setName( new Name( "Steven" ) );
32+
33+
Session session = openSession();
34+
session.getTransaction().begin();
35+
session.save( simpleEntity );
36+
session.getTransaction().commit();
37+
session.close();
38+
39+
session = openSession();
40+
session.getTransaction().begin();
41+
SomeEntity loaded = session.byId( SomeEntity.class ).load( 1L );
42+
loaded.setName( new Name( "Steve" ) );
43+
session.getTransaction().commit();
44+
session.close();
45+
46+
session = openSession();
47+
session.getTransaction().begin();
48+
loaded = session.byId( SomeEntity.class ).load( 1L );
49+
assertEquals( "Steve", loaded.getName().getText() );
50+
session.delete( loaded );
51+
session.getTransaction().commit();
52+
session.close();
53+
}
54+
55+
@Test
56+
public void dirtyCheckAgainstMutatedNameInstance() {
57+
SomeEntity simpleEntity = new SomeEntity();
58+
simpleEntity.setId( 1L );
59+
simpleEntity.setName( new Name( "Steven" ) );
60+
61+
Session session = openSession();
62+
session.getTransaction().begin();
63+
session.save( simpleEntity );
64+
session.getTransaction().commit();
65+
session.close();
66+
67+
session = openSession();
68+
session.getTransaction().begin();
69+
SomeEntity loaded = session.byId( SomeEntity.class ).load( 1L );
70+
loaded.getName().setText( "Steve" );
71+
session.getTransaction().commit();
72+
session.close();
73+
74+
session = openSession();
75+
session.getTransaction().begin();
76+
loaded = session.byId( SomeEntity.class ).load( 1L );
77+
assertEquals( "Steve", loaded.getName().getText() );
78+
session.delete( loaded );
79+
session.getTransaction().commit();
80+
session.close();
81+
}
82+
83+
@Test
84+
public void dirtyCheckAgainstNewNumberInstance() {
85+
// numbers (and most other java types) are actually immutable...
86+
SomeEntity simpleEntity = new SomeEntity();
87+
simpleEntity.setId( 1L );
88+
simpleEntity.setNumber( 1 );
89+
90+
Session session = openSession();
91+
session.getTransaction().begin();
92+
session.save( simpleEntity );
93+
session.getTransaction().commit();
94+
session.close();
95+
96+
session = openSession();
97+
session.getTransaction().begin();
98+
SomeEntity loaded = session.byId( SomeEntity.class ).load( 1L );
99+
loaded.setNumber( 2 );
100+
session.getTransaction().commit();
101+
session.close();
102+
103+
session = openSession();
104+
session.getTransaction().begin();
105+
loaded = session.byId( SomeEntity.class ).load( 1L );
106+
assertEquals( 2, loaded.getNumber().intValue() );
107+
session.delete( loaded );
108+
session.getTransaction().commit();
109+
session.close();
110+
}
111+
112+
@Override
113+
protected Class[] getAnnotatedClasses() {
114+
return new Class[] {SomeEntity.class};
115+
}
116+
117+
public static class Name {
118+
private String text;
119+
120+
public Name() {
121+
}
122+
123+
public Name(String text) {
124+
this.text = text;
125+
}
126+
127+
public String getText() {
128+
return text;
129+
}
130+
131+
public void setText(String text) {
132+
this.text = text;
133+
}
134+
}
135+
136+
public static class NameConverter implements AttributeConverter<Name, String> {
137+
public String convertToDatabaseColumn(Name name) {
138+
return name == null ? null : name.getText();
139+
}
140+
141+
public Name convertToEntityAttribute(String s) {
142+
return s == null ? null : new Name( s );
143+
}
144+
}
145+
146+
public static class IntegerConverter implements AttributeConverter<Integer, String> {
147+
public String convertToDatabaseColumn(Integer value) {
148+
return value == null ? null : value.toString();
149+
}
150+
151+
public Integer convertToEntityAttribute(String s) {
152+
return s == null ? null : Integer.parseInt( s );
153+
}
154+
}
155+
156+
@Entity(name = "SomeEntity")
157+
public static class SomeEntity {
158+
@Id
159+
private Long id;
160+
161+
@Convert(converter = IntegerConverter.class)
162+
@Column(name = "num")
163+
private Integer number;
164+
165+
@Convert(converter = NameConverter.class)
166+
@Column(name = "name")
167+
private Name name = new Name();
168+
169+
public Long getId() {
170+
return id;
171+
}
172+
173+
public void setId(Long id) {
174+
this.id = id;
175+
}
176+
177+
public Integer getNumber() {
178+
return number;
179+
}
180+
181+
public void setNumber(Integer number) {
182+
this.number = number;
183+
}
184+
185+
public Name getName() {
186+
return name;
187+
}
188+
189+
public void setName(Name name) {
190+
this.name = name;
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)