1
+ /*
2
+ * Copyright 2025 the original author or authors.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ package org .springframework .data .jpa .repository .query ;
17
+
18
+ import static org .assertj .core .api .Assertions .*;
19
+
20
+ import jakarta .persistence .Entity ;
21
+ import jakarta .persistence .EntityManager ;
22
+ import jakarta .persistence .GeneratedValue ;
23
+ import jakarta .persistence .Id ;
24
+ import jakarta .persistence .PersistenceContext ;
25
+ import jakarta .persistence .Table ;
26
+ import jakarta .persistence .criteria .CriteriaBuilder ;
27
+ import jakarta .persistence .criteria .CriteriaQuery ;
28
+ import jakarta .persistence .criteria .Root ;
29
+
30
+ import java .io .Serializable ;
31
+ import java .lang .reflect .Method ;
32
+
33
+ import org .hibernate .annotations .Any ;
34
+ import org .hibernate .annotations .AnyDiscriminator ;
35
+ import org .hibernate .annotations .AnyDiscriminatorValue ;
36
+ import org .hibernate .annotations .AnyKeyJavaClass ;
37
+ import org .hibernate .annotations .DiscriminatorType ;
38
+ import org .junit .jupiter .api .Test ;
39
+ import org .junit .jupiter .api .extension .ExtendWith ;
40
+ import org .springframework .beans .factory .annotation .Autowired ;
41
+ import org .springframework .context .annotation .Bean ;
42
+ import org .springframework .context .annotation .Configuration ;
43
+ import org .springframework .data .jpa .domain .support .AuditingEntityListener ;
44
+ import org .springframework .data .jpa .repository .JpaRepository ;
45
+ import org .springframework .data .jpa .repository .config .EnableJpaRepositories ;
46
+ import org .springframework .data .jpa .repository .support .JpaRepositoryFactory ;
47
+ import org .springframework .data .mapping .PropertyPath ;
48
+ import org .springframework .data .repository .query .QueryMethod ;
49
+ import org .springframework .test .context .ContextConfiguration ;
50
+ import org .springframework .test .context .junit .jupiter .SpringExtension ;
51
+ import org .springframework .transaction .annotation .Transactional ;
52
+
53
+ import com .zaxxer .hikari .HikariDataSource ;
54
+
55
+ import org .springframework .orm .jpa .JpaTransactionManager ;
56
+ import org .springframework .orm .jpa .LocalContainerEntityManagerFactoryBean ;
57
+ import org .springframework .orm .jpa .vendor .HibernateJpaVendorAdapter ;
58
+
59
+ /**
60
+ * Integration tests for Hibernate @Any annotation support in query derivation.
61
+ *
62
+ * @author Hyunjoon Choi
63
+ */
64
+ @ ExtendWith (SpringExtension .class )
65
+ @ ContextConfiguration (classes = HibernateAnyAnnotationIntegrationTests .TestConfig .class )
66
+ @ Transactional
67
+ class HibernateAnyAnnotationIntegrationTests {
68
+
69
+ @ PersistenceContext EntityManager em ;
70
+ @ Autowired TestEntityRepository repository ;
71
+
72
+ @ Test
73
+ void shouldHandleAnyAnnotationInQueryDerivation () {
74
+
75
+ // Given
76
+ MonitorableEntity monitorable = new MonitorableEntity ();
77
+ monitorable .name = "Test Entity" ;
78
+ em .persist (monitorable );
79
+
80
+ TestEntity entity = new TestEntity ();
81
+ entity .monitorObject = monitorable ;
82
+ entity = repository .save (entity );
83
+
84
+ em .flush ();
85
+ em .clear ();
86
+
87
+ // When
88
+ var result = repository .findByMonitorObjectId (monitorable .id );
89
+
90
+ // Then
91
+ assertThat (result ).isNotEmpty ();
92
+ assertThat (result .get (0 ).id ).isEqualTo (entity .id );
93
+ }
94
+
95
+ @ Test
96
+ void shouldReturnEmptyWhenMonitorObjectIsNull () {
97
+
98
+ // Given
99
+ TestEntity entity = new TestEntity ();
100
+ entity .monitorObject = null ;
101
+ repository .save (entity );
102
+
103
+ em .flush ();
104
+ em .clear ();
105
+
106
+ // When
107
+ var result = repository .findByMonitorObjectId (999L );
108
+
109
+ // Then
110
+ assertThat (result ).isEmpty ();
111
+ }
112
+
113
+ @ Test
114
+ void isAnyAnnotatedPropertyReflectionTest () throws Exception {
115
+
116
+ // Use reflection to test the private method
117
+ Method method = QueryUtils .class .getDeclaredMethod ("isAnyAnnotatedProperty" ,
118
+ jakarta .persistence .criteria .From .class , PropertyPath .class );
119
+ method .setAccessible (true );
120
+
121
+ CriteriaBuilder cb = em .getCriteriaBuilder ();
122
+ CriteriaQuery <TestEntity > query = cb .createQuery (TestEntity .class );
123
+ Root <TestEntity > root = query .from (TestEntity .class );
124
+
125
+ PropertyPath propertyPath = PropertyPath .from ("monitorObject" , TestEntity .class );
126
+
127
+ // When
128
+ Boolean result = (Boolean ) method .invoke (null , root , propertyPath );
129
+
130
+ // Then
131
+ assertThat (result ).isTrue ();
132
+ }
133
+
134
+ @ Test
135
+ void regularPropertyShouldNotBeDetectedAsAny () throws Exception {
136
+
137
+ // Use reflection to test the private method
138
+ Method method = QueryUtils .class .getDeclaredMethod ("isAnyAnnotatedProperty" ,
139
+ jakarta .persistence .criteria .From .class , PropertyPath .class );
140
+ method .setAccessible (true );
141
+
142
+ CriteriaBuilder cb = em .getCriteriaBuilder ();
143
+ CriteriaQuery <TestEntity > query = cb .createQuery (TestEntity .class );
144
+ Root <TestEntity > root = query .from (TestEntity .class );
145
+
146
+ PropertyPath propertyPath = PropertyPath .from ("id" , TestEntity .class );
147
+
148
+ // When
149
+ Boolean result = (Boolean ) method .invoke (null , root , propertyPath );
150
+
151
+ // Then
152
+ assertThat (result ).isFalse ();
153
+ }
154
+
155
+ @ Configuration
156
+ @ EnableJpaRepositories (considerNestedRepositories = true )
157
+ static class TestConfig {
158
+
159
+ @ Bean
160
+ HikariDataSource dataSource () {
161
+ HikariDataSource dataSource = new HikariDataSource ();
162
+ dataSource .setJdbcUrl ("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1" );
163
+ dataSource .setUsername ("sa" );
164
+ dataSource .setPassword ("" );
165
+ return dataSource ;
166
+ }
167
+
168
+ @ Bean
169
+ LocalContainerEntityManagerFactoryBean entityManagerFactory (HikariDataSource dataSource ) {
170
+ HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter ();
171
+ vendorAdapter .setGenerateDdl (true );
172
+ vendorAdapter .setShowSql (false );
173
+
174
+ LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean ();
175
+ factory .setJpaVendorAdapter (vendorAdapter );
176
+ factory .setDataSource (dataSource );
177
+ factory .setPackagesToScan (HibernateAnyAnnotationIntegrationTests .class .getPackage ().getName ());
178
+ return factory ;
179
+ }
180
+
181
+ @ Bean
182
+ JpaTransactionManager transactionManager (LocalContainerEntityManagerFactoryBean entityManagerFactory ) {
183
+ JpaTransactionManager transactionManager = new JpaTransactionManager ();
184
+ transactionManager .setEntityManagerFactory (entityManagerFactory .getObject ());
185
+ return transactionManager ;
186
+ }
187
+ }
188
+
189
+ @ Entity
190
+ @ Table (name = "test_entity" )
191
+ static class TestEntity {
192
+
193
+ @ Id
194
+ @ GeneratedValue
195
+ Long id ;
196
+
197
+ @ Any
198
+ @ AnyDiscriminator (DiscriminatorType .STRING )
199
+ @ AnyDiscriminatorValue (discriminator = "monitorable" , entity = MonitorableEntity .class )
200
+ @ AnyKeyJavaClass (Long .class )
201
+ Object monitorObject ;
202
+ }
203
+
204
+ @ Entity
205
+ @ Table (name = "monitorable_entity" )
206
+ static class MonitorableEntity {
207
+
208
+ @ Id
209
+ @ GeneratedValue
210
+ Long id ;
211
+
212
+ String name ;
213
+ }
214
+
215
+ interface TestEntityRepository extends JpaRepository <TestEntity , Long > {
216
+ java .util .List <TestEntity > findByMonitorObjectId (Long id );
217
+ }
218
+ }
0 commit comments