Skip to content

Commit 1d149ac

Browse files
committed
HHH-16991 Fix array based id restriction with EnhanceUserType
1 parent 110c284 commit 1d149ac

File tree

2 files changed

+326
-7
lines changed

2 files changed

+326
-7
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,12 @@ public PersistentCollection<?> load(Object key, SharedSessionContractImplementor
116116
MULTI_KEY_LOAD_LOGGER.debugf( "Batch loading entity `%s#%s`", getLoadable().getNavigableRole().getFullPath(), key );
117117
}
118118
final ForeignKeyDescriptor keyDescriptor = getLoadable().getKeyDescriptor();
119-
if ( keyDescriptor.isEmbedded() ) {
119+
if ( keyDescriptor.isEmbedded()
120+
|| keyDescriptor.getKeyPart().getSingleJdbcMapping().getValueConverter() != null ) {
120121
assert keyDescriptor.getJdbcTypeCount() == 1;
121-
return loadEmbeddable( key, session, keyDescriptor );
122+
return loadWithConversion( key, session, keyDescriptor );
122123
}
123124
else {
124-
125125
final Object[] keysToInitialize = resolveKeysToInitialize( key, session );
126126
initializeKeys( keysToInitialize, session );
127127

@@ -134,7 +134,7 @@ public PersistentCollection<?> load(Object key, SharedSessionContractImplementor
134134
}
135135
}
136136

137-
private PersistentCollection<?> loadEmbeddable(
137+
private PersistentCollection<?> loadWithConversion(
138138
Object keyBeingLoaded,
139139
SharedSessionContractImplementor session,
140140
ForeignKeyDescriptor keyDescriptor) {
@@ -148,14 +148,14 @@ private PersistentCollection<?> loadEmbeddable(
148148
.getComponentType(),
149149
length
150150
);
151-
final Object[] embeddedKeys = (Object[]) Array.newInstance( keyDomainType, length );
151+
final Object[] domainKeys = (Object[]) Array.newInstance( keyDomainType, length );
152152
session.getPersistenceContextInternal().getBatchFetchQueue()
153153
.collectBatchLoadableCollectionKeys(
154154
length,
155155
(index, key) ->
156156
keyDescriptor.forEachJdbcValue( key, (i, value, jdbcMapping) -> {
157157
jdbcKeysToInitialize[index] = value;
158-
embeddedKeys[index] = key;
158+
domainKeys[index] = key;
159159
}, session )
160160
,
161161
keyBeingLoaded,
@@ -164,7 +164,7 @@ private PersistentCollection<?> loadEmbeddable(
164164

165165
initializeKeys( jdbcKeysToInitialize, session );
166166

167-
for ( Object initializedKey : embeddedKeys ) {
167+
for ( Object initializedKey : domainKeys ) {
168168
finishInitializingKey( initializedKey, session );
169169
}
170170

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

0 commit comments

Comments
 (0)