Skip to content

Commit 3348e74

Browse files
committed
Add native support for @convert on JPA entities
This commit infers the reflection hints required for converters when they are specified with the @convert annotation at class or field level. It also refines the hints generated for @converter in order to just generate the construct hint, not the public method one which should be not needed. Closes gh-29771
1 parent e2832ea commit 3348e74

File tree

7 files changed

+183
-6
lines changed

7 files changed

+183
-6
lines changed

spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessor.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323

2424
import javax.lang.model.element.Modifier;
2525

26+
import jakarta.persistence.Convert;
2627
import jakarta.persistence.Converter;
2728
import jakarta.persistence.EntityListeners;
2829
import jakarta.persistence.IdClass;
@@ -156,9 +157,20 @@ private void contributeIdClassHints(RuntimeHints hints, Class<?> managedClass) {
156157

157158
private void contributeConverterHints(RuntimeHints hints, Class<?> managedClass) {
158159
Converter converter = AnnotationUtils.findAnnotation(managedClass, Converter.class);
160+
ReflectionHints reflectionHints = hints.reflection();
159161
if (converter != null) {
160-
hints.reflection().registerType(managedClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
162+
reflectionHints.registerType(managedClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
161163
}
164+
Convert convertClassAnnotation = AnnotationUtils.findAnnotation(managedClass, Convert.class);
165+
if (convertClassAnnotation != null) {
166+
reflectionHints.registerType(convertClassAnnotation.converter(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
167+
}
168+
ReflectionUtils.doWithFields(managedClass, field -> {
169+
Convert convertFieldAnnotation = AnnotationUtils.findAnnotation(field, Convert.class);
170+
if (convertFieldAnnotation != null && convertFieldAnnotation.converter() != void.class) {
171+
reflectionHints.registerType(convertFieldAnnotation.converter(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
172+
}
173+
});
162174
}
163175

164176
private void contributeCallbackHints(RuntimeHints hints, Class<?> managedClass) {

spring-orm/src/test/java/org/springframework/orm/jpa/domain/Employee.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,13 +17,15 @@
1717
package org.springframework.orm.jpa.domain;
1818

1919
import jakarta.persistence.Column;
20+
import jakarta.persistence.Convert;
2021
import jakarta.persistence.Entity;
2122
import jakarta.persistence.Id;
2223
import jakarta.persistence.IdClass;
2324
import jakarta.persistence.PreRemove;
2425

2526
@Entity
2627
@IdClass(EmployeeId.class)
28+
@Convert(converter = EmployeeKindConverter.class, attributeName = "kind")
2729
public class Employee {
2830

2931
@Id
@@ -36,6 +38,10 @@ public class Employee {
3638

3739
private EmployeeLocation location;
3840

41+
@Convert(converter = EmployeeCategoryConverter.class)
42+
private EmployeeCategory category;
43+
44+
private EmployeeKind kind;
3945

4046
public String getName() {
4147
return name;
@@ -61,6 +67,22 @@ public void setLocation(EmployeeLocation location) {
6167
this.location = location;
6268
}
6369

70+
public EmployeeCategory getCategory() {
71+
return category;
72+
}
73+
74+
public void setCategory(EmployeeCategory category) {
75+
this.category = category;
76+
}
77+
78+
public EmployeeKind getKind() {
79+
return kind;
80+
}
81+
82+
public void setKind(EmployeeKind kind) {
83+
this.kind = kind;
84+
}
85+
6486
@PreRemove
6587
public void preRemove() {
6688
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2002-2023 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+
17+
package org.springframework.orm.jpa.domain;
18+
19+
public class EmployeeCategory {
20+
private String name;
21+
22+
public String getName() {
23+
return name;
24+
}
25+
26+
public void setName(String name) {
27+
this.name = name;
28+
}
29+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2023 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+
17+
package org.springframework.orm.jpa.domain;
18+
19+
import jakarta.persistence.AttributeConverter;
20+
21+
public class EmployeeCategoryConverter implements AttributeConverter<EmployeeCategory, String> {
22+
23+
@Override
24+
public String convertToDatabaseColumn(EmployeeCategory employeeCategory) {
25+
if (employeeCategory != null) {
26+
return employeeCategory.getName();
27+
}
28+
return null;
29+
}
30+
31+
@Override
32+
public EmployeeCategory convertToEntityAttribute(String data) {
33+
if (data != null) {
34+
EmployeeCategory employeeCategory = new EmployeeCategory();
35+
employeeCategory.setName(data);
36+
return employeeCategory;
37+
}
38+
return null;
39+
}
40+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2002-2023 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+
17+
package org.springframework.orm.jpa.domain;
18+
19+
public class EmployeeKind {
20+
private String name;
21+
22+
public String getName() {
23+
return name;
24+
}
25+
26+
public void setName(String name) {
27+
this.name = name;
28+
}
29+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2023 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+
17+
package org.springframework.orm.jpa.domain;
18+
19+
import jakarta.persistence.AttributeConverter;
20+
21+
public class EmployeeKindConverter implements AttributeConverter<EmployeeKind, String> {
22+
23+
@Override
24+
public String convertToDatabaseColumn(EmployeeKind employeeKind) {
25+
if (employeeKind != null) {
26+
return employeeKind.getName();
27+
}
28+
return null;
29+
}
30+
31+
@Override
32+
public EmployeeKind convertToEntityAttribute(String data) {
33+
if (data != null) {
34+
EmployeeKind employeeKind = new EmployeeKind();
35+
employeeKind.setName(data);
36+
return employeeKind;
37+
}
38+
return null;
39+
}
40+
}

spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,7 +40,9 @@
4040
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
4141
import org.springframework.orm.jpa.domain.DriversLicense;
4242
import org.springframework.orm.jpa.domain.Employee;
43+
import org.springframework.orm.jpa.domain.EmployeeCategoryConverter;
4344
import org.springframework.orm.jpa.domain.EmployeeId;
45+
import org.springframework.orm.jpa.domain.EmployeeKindConverter;
4446
import org.springframework.orm.jpa.domain.EmployeeLocation;
4547
import org.springframework.orm.jpa.domain.EmployeeLocationConverter;
4648
import org.springframework.orm.jpa.domain.Person;
@@ -96,8 +98,11 @@ void contributeHints() {
9698
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeId.class)
9799
.withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints);
98100
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeLocationConverter.class)
99-
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS))
100-
.accepts(hints);
101+
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints);
102+
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeCategoryConverter.class)
103+
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints);
104+
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeKindConverter.class)
105+
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints);
101106
assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeLocation.class)
102107
.withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints);
103108
});

0 commit comments

Comments
 (0)