Skip to content

Commit 0f019d9

Browse files
committed
HHH-16991 Fix array based id restriction with EnhanceUserType
1 parent 9df82f7 commit 0f019d9

File tree

2 files changed

+314
-7
lines changed

2 files changed

+314
-7
lines changed

hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionBatchLoaderArrayParam.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,14 @@ public CollectionBatchLoaderArrayParam(
109109
.buildSelectTranslator( getSessionFactory(), sqlSelect )
110110
.translate( JdbcParameterBindings.NO_BINDINGS, QueryOptions.NONE );
111111
}
112+
112113
@Override
113114
public PersistentCollection<?> load(Object keyBeingLoaded, SharedSessionContractImplementor session) {
114115
final ForeignKeyDescriptor keyDescriptor = getLoadable().getKeyDescriptor();
115-
if ( keyDescriptor.isEmbedded() ) {
116+
if ( keyDescriptor.isEmbedded()
117+
|| keyDescriptor.getKeyPart().getSingleJdbcMapping().getValueConverter() != null ) {
116118
assert keyDescriptor.getJdbcTypeCount() == 1;
117-
return loadEmbeddable( keyBeingLoaded, session, keyDescriptor );
119+
return loadWithConversion( keyBeingLoaded, session, keyDescriptor );
118120
}
119121
else {
120122
return super.load( keyBeingLoaded, session );
@@ -123,7 +125,7 @@ public PersistentCollection<?> load(Object keyBeingLoaded, SharedSessionContract
123125
}
124126

125127
@AllowReflection
126-
private PersistentCollection<?> loadEmbeddable(
128+
private PersistentCollection<?> loadWithConversion(
127129
Object keyBeingLoaded,
128130
SharedSessionContractImplementor session,
129131
ForeignKeyDescriptor keyDescriptor) {
@@ -141,14 +143,14 @@ private PersistentCollection<?> loadEmbeddable(
141143
.getComponentType(),
142144
length
143145
);
144-
final Object[] embeddedKeys = (Object[]) newInstance( keyDomainType, length );
146+
final Object[] domainKeys = (Object[]) newInstance( keyDomainType, length );
145147
session.getPersistenceContextInternal().getBatchFetchQueue()
146148
.collectBatchLoadableCollectionKeys(
147149
length,
148150
(index, key) ->
149151
keyDescriptor.forEachJdbcValue( key, (i, value, jdbcMapping) -> {
150152
keysToInitialize[index] = value;
151-
embeddedKeys[index] = key;
153+
domainKeys[index] = key;
152154
}, session )
153155
,
154156
keyBeingLoaded,
@@ -163,7 +165,7 @@ private PersistentCollection<?> loadEmbeddable(
163165

164166
initializeKeys( keyBeingLoaded, keys, session );
165167

166-
for ( Object initializedKey : embeddedKeys ) {
168+
for ( Object initializedKey : domainKeys ) {
167169
if ( initializedKey != null ) {
168170
finishInitializingKey( initializedKey, session );
169171
}
@@ -222,7 +224,8 @@ void finishInitializingKeys(Object[] keys, SharedSessionContractImplementor sess
222224
@AllowReflection
223225
Object[] resolveKeysToInitialize(Object keyBeingLoaded, SharedSessionContractImplementor session) {
224226
final ForeignKeyDescriptor keyDescriptor = getLoadable().getKeyDescriptor();
225-
if( keyDescriptor.isEmbedded()){
227+
if ( keyDescriptor.isEmbedded()
228+
|| keyDescriptor.getKeyPart().getSingleJdbcMapping().getValueConverter() != null ) {
226229
assert keyDescriptor.getJdbcTypeCount() == 1;
227230
final int length = getDomainBatchSize();
228231
final Object[] keysToInitialize = (Object[]) newInstance( keyDescriptor.getSingleJdbcMapping().getJdbcJavaType().getJavaTypeClass(), length );
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.batch;
6+
7+
import jakarta.persistence.AttributeConverter;
8+
import jakarta.persistence.CascadeType;
9+
import jakarta.persistence.Entity;
10+
import jakarta.persistence.FetchType;
11+
import jakarta.persistence.Id;
12+
import jakarta.persistence.JoinColumn;
13+
import jakarta.persistence.ManyToOne;
14+
import jakarta.persistence.OneToMany;
15+
import jakarta.persistence.Table;
16+
import org.assertj.core.api.Assertions;
17+
import org.hibernate.HibernateException;
18+
import org.hibernate.annotations.BatchSize;
19+
import org.hibernate.annotations.Type;
20+
import org.hibernate.cfg.AvailableSettings;
21+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
22+
import org.hibernate.testing.jdbc.SQLStatementInspector;
23+
import org.hibernate.testing.orm.junit.DomainModel;
24+
import org.hibernate.testing.orm.junit.ServiceRegistry;
25+
import org.hibernate.testing.orm.junit.SessionFactory;
26+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
27+
import org.hibernate.testing.orm.junit.Setting;
28+
import org.hibernate.usertype.EnhancedUserType;
29+
import org.junit.jupiter.api.BeforeAll;
30+
import org.junit.jupiter.api.Test;
31+
32+
import java.io.Serializable;
33+
import java.sql.PreparedStatement;
34+
import java.sql.ResultSet;
35+
import java.sql.SQLException;
36+
import java.util.HashSet;
37+
import java.util.List;
38+
import java.util.Objects;
39+
import java.util.Set;
40+
41+
import static org.hibernate.type.SqlTypes.VARCHAR;
42+
43+
@ServiceRegistry(
44+
settings = {
45+
@Setting( name = AvailableSettings.DIALECT_NATIVE_PARAM_MARKERS, value = "false" )
46+
}
47+
)
48+
@DomainModel(
49+
annotatedClasses = {
50+
BatchAndUserTypeIdCollectionTest.Child.class,
51+
BatchAndUserTypeIdCollectionTest.Parent.class
52+
}
53+
)
54+
@SessionFactory(
55+
useCollectingStatementInspector = true
56+
)
57+
58+
public class BatchAndUserTypeIdCollectionTest {
59+
60+
@BeforeAll
61+
public void setUp(SessionFactoryScope scope) {
62+
scope.inTransaction(
63+
session -> {
64+
for (long i = 1L; i < 11; i++) {
65+
Parent parent = new Parent( new Parent.ParentId( "parent-" + i ) );
66+
Child child1 = new Child( i * 100L + 1L, parent );
67+
Child child2 = new Child( i * 100L + 2L, parent );
68+
Child child3 = new Child( i * 100L + 3L, parent );
69+
Child child4 = new Child( i * 100L + 4L, parent );
70+
Child child5 = new Child( i * 100L + 5L, parent );
71+
Child child6 = new Child( i * 100L + 6L, parent );
72+
Child child7 = new Child( i * 100L + 7L, parent );
73+
Child child8 = new Child( i * 100L + 8L, parent );
74+
Child child9 = new Child( i * 100L + 9L, parent );
75+
Child child10 = new Child( i * 100L + 10L, parent );
76+
Child child11 = new Child( i * 100L + 11L, parent );
77+
session.persist( parent );
78+
}
79+
}
80+
);
81+
}
82+
83+
@Test
84+
public void testBatchInitializeChildCollection(SessionFactoryScope scope){
85+
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
86+
scope.inTransaction(
87+
session -> {
88+
statementInspector.clear();
89+
final List<Parent> list = session.createSelectionQuery( "from Parent", Parent.class )
90+
.getResultList();
91+
list.get( 0 ).getChildren().size();
92+
statementInspector.assertExecutedCount( 2 );
93+
Assertions.assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( "?" );
94+
if ( scope.getSessionFactory().getJdbcServices().getDialect().useArrayForMultiValuedParameters() ) {
95+
Assertions.assertThat( statementInspector.getSqlQueries().get( 1 ) ).containsOnlyOnce( "?" );
96+
}
97+
else {
98+
Assertions.assertThat( statementInspector.getSqlQueries().get( 1 ) ).containsOnlyOnce( "in (?,?,?,?,?)" );
99+
}
100+
}
101+
);
102+
}
103+
104+
@Entity(name = "Child")
105+
@Table(name = "child_table")
106+
public static class Child {
107+
@Id
108+
private Long id;
109+
110+
private String name;
111+
112+
@ManyToOne
113+
@JoinColumn(name = "parent_id")
114+
private Parent parent;
115+
116+
public Child() {
117+
}
118+
119+
public Child(Long id, Parent parent) {
120+
this.id = id;
121+
this.name = String.valueOf( id );
122+
this.parent = parent;
123+
parent.addChild( this );
124+
}
125+
126+
public Long getId() {
127+
return id;
128+
}
129+
130+
public String getName() {
131+
return name;
132+
}
133+
134+
public Parent getParent() {
135+
return parent;
136+
}
137+
138+
}
139+
140+
@Entity(name = "Parent")
141+
@Table(name = "parents")
142+
public static class Parent {
143+
@Id
144+
@Type(ParentIdUserType.class)
145+
private ParentId id;
146+
147+
private String name;
148+
149+
@BatchSize(size = 5)
150+
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
151+
public Set<Child> children = new HashSet<>();
152+
153+
public Parent() {
154+
}
155+
156+
public Parent(ParentId id) {
157+
this.id = id;
158+
this.name = String.valueOf( id );
159+
}
160+
161+
public ParentId getId() {
162+
return id;
163+
}
164+
165+
public String getName() {
166+
return name;
167+
}
168+
169+
public Set<Child> getChildren() {
170+
return children;
171+
}
172+
173+
public void addChild(Child child){
174+
children.add( child );
175+
}
176+
177+
public static class ParentId implements Serializable {
178+
179+
private static final long serialVersionUID = 1L;
180+
181+
private final String id;
182+
183+
@Override
184+
public String toString() {
185+
return id;
186+
}
187+
188+
public ParentId(String id) {
189+
this.id = id;
190+
}
191+
192+
public String getId() {
193+
return id;
194+
}
195+
196+
@Override
197+
public boolean equals(Object o) {
198+
if ( o == null || getClass() != o.getClass() ) {
199+
return false;
200+
}
201+
ParentId parentId = (ParentId) o;
202+
return Objects.equals( id, parentId.id );
203+
}
204+
205+
@Override
206+
public int hashCode() {
207+
return Objects.hashCode( id );
208+
}
209+
}
210+
211+
212+
static class ParentIdUserType implements EnhancedUserType<ParentId> {
213+
@Override
214+
public int getSqlType() {
215+
return VARCHAR;
216+
}
217+
218+
@Override
219+
public Class<ParentId> returnedClass() {
220+
return ParentId.class;
221+
}
222+
223+
@Override
224+
public boolean equals(ParentId x, ParentId y) {
225+
return Objects.equals(x, y);
226+
}
227+
228+
@Override
229+
public int hashCode(ParentId x) {
230+
return x.hashCode();
231+
}
232+
233+
@Override
234+
public ParentId nullSafeGet(ResultSet rs, int position,
235+
SharedSessionContractImplementor session, Object owner)
236+
throws SQLException {
237+
String string = rs.getString( position );
238+
return rs.wasNull() ? null : new ParentId(string);
239+
}
240+
241+
@Override
242+
public void nullSafeSet(PreparedStatement st, ParentId value, int index,
243+
SharedSessionContractImplementor session)
244+
throws SQLException {
245+
if ( value == null ) {
246+
st.setNull(index, VARCHAR);
247+
}
248+
else {
249+
st.setString(index, value.getId());
250+
}
251+
}
252+
253+
@Override
254+
public boolean isMutable() {
255+
return false;
256+
}
257+
258+
@Override
259+
public ParentId deepCopy(ParentId parentId) {
260+
return parentId; //ParentId is immutable
261+
}
262+
263+
@Override
264+
public Serializable disassemble(ParentId parentId) {
265+
return parentId; //ParentId is immutable
266+
}
267+
268+
@Override
269+
public ParentId assemble(Serializable cached, Object owner) {
270+
return (ParentId) cached; //ParentId is immutable
271+
}
272+
273+
@Override
274+
public String toSqlLiteral(ParentId parentId) {
275+
return parentId.getId();
276+
}
277+
278+
@Override
279+
public String toString(ParentId parentId) throws HibernateException {
280+
return parentId.getId();
281+
}
282+
283+
@Override
284+
public ParentId fromStringValue(CharSequence sequence) throws HibernateException {
285+
return new ParentId(sequence.toString());
286+
}
287+
288+
@Override
289+
public AttributeConverter<ParentId, ?> getValueConverter() {
290+
return new AttributeConverter<ParentId, String>() {
291+
@Override
292+
public String convertToDatabaseColumn(ParentId value) {
293+
return value == null ? null : value.getId();
294+
}
295+
296+
@Override
297+
public ParentId convertToEntityAttribute(String dbData) {
298+
return dbData == null ? null : new ParentId(dbData);
299+
}
300+
};
301+
}
302+
}
303+
}
304+
}

0 commit comments

Comments
 (0)