Skip to content

Commit 433ec8b

Browse files
committed
HHH-18198 - Remove @target
1 parent 50a4444 commit 433ec8b

File tree

6 files changed

+108
-131
lines changed

6 files changed

+108
-131
lines changed

documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -299,60 +299,39 @@ This usage is covered in detail in <<chapters/domain/identifiers.adoc#identifier
299299
Embeddable types that are used as collection entries, map keys or entity type identifiers cannot include their own collection mappings.
300300
====
301301

302+
302303
[[embeddable-Target]]
303304
==== `@TargetEmbeddable` mapping
304305

305-
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/TargetEmbeddable.html[`@TargetEmbeddable`] annotation is used to specify the implementation class of an embeddable-valued mapping when the declared type is a non-concrete type (interface, etc.).
306-
307-
[NOTE]
308-
====
309-
An "embeddable-valued mapping" may be an `@EmbeddedId`, `@Embedded` or `@ElementCollection`.
310-
====
306+
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/TargetEmbeddable.html[`@TargetEmbeddable`] annotation is used to specify the implementation class of an embeddable-valued mapping when the declared type is a non-concrete type (interface, etc).
311307

312-
`@TargetEmbeddable` may be specified on the embeddable-valued mapping, as shown in <<embeddable-Target-example>>, or it may be defined on the interface/class used as the declared type, illustrated in <<embeddable-Target-example2>>.
308+
As an example, consider an `@Embedded` attribute (`City.coordinates`) whose declared type (`Coordinates`) is an interface. However, Hibernate needs to know about the concrete type to use, which is `GPS` in this case. The `@TargetEmbeddable` annotation is used to provide this information.
313309

314310
[[embeddable-Target-example]]
315-
.Embeddable-valued mapping usage
311+
.Embedded mapping example
316312
====
317313
[source, java, indent=0]
318314
----
319315
include::{example-dir-emeddable}/TargetEmbeddableOnEmbeddedTest.java[tags=embeddable-Target-example]
320316
----
321317
====
322318

323-
The `coordinates` embeddable type is mapped as the `Coordinates` interface.
324-
However, Hibernate needs to know the actual implementation type, which is `GPS` in this case,
325-
hence the `@TargetEmbeddable` annotation is used to provide this information.
319+
The mapping works just like any embeddable-valued mapping at this point, with Hibernate using `GPS` "under the covers":
326320

327-
Assuming we have persisted the following `City` entity:
328-
329-
[[embeddable-Target-persist-example]]
330-
.`@TargetEmbeddable` persist example
331-
====
332-
[source, java, indent=0]
333-
----
334-
include::{example-dir-emeddable}/TargetEmbeddableOnEmbeddedTest.java[tags=embeddable-Target-persist-example]
335-
----
336-
====
337-
338-
When fetching the `City` entity, the `coordinates` property is mapped by the `@TargetEmbeddable` expression:
339321

340322
[[embeddable-Target-fetching-example]]
341-
.`@TargetEmbeddable` fetching example
323+
.Usage example
342324
====
343325
[source, java, indent=0]
344326
----
345327
include::{example-dir-emeddable}/TargetEmbeddableOnEmbeddedTest.java[tags=embeddable-Target-fetching-example]
346328
----
347-
348-
[source, SQL, indent=0]
349-
----
350-
include::{extrasdir}/embeddable/embeddable-Target-fetching-example.sql[]
351-
----
352329
====
353330

331+
For `@Embedded` and `@EmbeddedId` cases, `@TargetEmbeddable` may be specified on the member's declared type instead of the member itself:
332+
354333
[[embeddable-Target-example2]]
355-
.Interface mapping usage
334+
.Interface mapping example
356335
====
357336
[source, java, indent=0]
358337
----

documentation/src/main/asciidoc/userguide/chapters/domain/extras/embeddable/embeddable-Target-fetching-example.sql

Lines changed: 0 additions & 15 deletions
This file was deleted.

hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyInferredData.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.boot.model.internal;
66

77
import jakarta.persistence.Access;
8+
import jakarta.persistence.ElementCollection;
89
import jakarta.persistence.Embedded;
910
import org.hibernate.MappingException;
1011
import org.hibernate.annotations.TargetEmbeddable;
@@ -22,6 +23,8 @@
2223
import org.hibernate.models.spi.TypeDetails.Kind;
2324
import org.hibernate.models.spi.TypeVariableScope;
2425

26+
import java.util.Locale;
27+
2528
/**
2629
* Retrieve all inferred data from an annotated element
2730
*
@@ -93,27 +96,42 @@ public TypeDetails getPropertyType() throws MappingException {
9396
return new ClassTypeDetailsImpl( classDetails, Kind.CLASS );
9497
}
9598

96-
final TargetEmbeddable targetEmbeddable = getTargetEmbeddableAnnotation(propertyMember);
99+
final TargetEmbeddable targetEmbeddable = getTargetEmbeddableAnnotation( propertyMember );
97100
if ( targetEmbeddable != null ) {
98101
return resolveTargetEmbeddableAnnotation( targetEmbeddable, sourceModelContext );
99102
}
100103

101104
return propertyMember.resolveRelativeType( ownerType );
102105
}
103106

104-
private TargetEmbeddable getTargetEmbeddableAnnotation(MemberDetails memberDetails) {
105-
TargetEmbeddable targetEmbeddable = memberDetails.getDirectAnnotationUsage( TargetEmbeddable.class );
106-
if ( targetEmbeddable != null ) {
107-
if (!memberDetails.hasDirectAnnotationUsage(Embedded.class) ) {
108-
// If set on an attribute, it should be an @Embedded
109-
throw new MappingException( "The @TargetEmbeddable annotation can only be set on an element marked with @Embedded" );
107+
private static TargetEmbeddable getTargetEmbeddableAnnotation(MemberDetails memberDetails) {
108+
// first we look for the annotation on the member itself
109+
final TargetEmbeddable memberAnnotation = memberDetails.getDirectAnnotationUsage( TargetEmbeddable.class );
110+
if ( memberAnnotation != null ) {
111+
// this should only be allowed for embedded or collections-of-embeddables.
112+
// NOTE: this is tricky to check for collections-of-embeddables as it
113+
// would require a deep check of the collection's component type, so
114+
// for now just assume the mapping is correct if we find @ElementCollection
115+
final boolean allowed = memberDetails.hasDirectAnnotationUsage( Embedded.class )
116+
|| memberDetails.hasDirectAnnotationUsage( ElementCollection.class );
117+
if ( !allowed ) {
118+
throw new MappingException(
119+
String.format(
120+
Locale.ROOT,
121+
"@TargetEmbeddable can only be specified on properties marked with @Embedded or @ElementCollection [%s$%s]",
122+
memberDetails.getDeclaringType().getName(),
123+
memberDetails.getName()
124+
)
125+
);
110126
}
127+
128+
return memberAnnotation;
111129
}
112-
else {
113-
// Set on an interface
114-
targetEmbeddable = memberDetails.getAssociatedType().determineRawClass().getDirectAnnotationUsage( TargetEmbeddable.class );
115-
}
116-
return targetEmbeddable;
130+
131+
// then, look on the member's type
132+
// NOTE: this is not supported for collections-of-embeddables
133+
// for the same reason stated above
134+
return memberDetails.getAssociatedType().determineRawClass().getDirectAnnotationUsage( TargetEmbeddable.class );
117135
}
118136

119137
private static ClassTypeDetailsImpl resolveTargetEmbeddableAnnotation(

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/TargetEmbeddableOnEmbeddedTest.java

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,60 +9,52 @@
99
import jakarta.persistence.Entity;
1010
import jakarta.persistence.GeneratedValue;
1111
import jakarta.persistence.Id;
12-
1312
import org.hibernate.annotations.TargetEmbeddable;
14-
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
15-
13+
import org.hibernate.testing.orm.junit.DomainModel;
14+
import org.hibernate.testing.orm.junit.SessionFactory;
15+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
1616
import org.junit.Test;
1717

18-
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
1918
import static org.junit.Assert.assertEquals;
2019

2120
/**
22-
* @author Vlad Mihalcea
21+
* @author Jan Schatteman
2322
*/
24-
public class TargetEmbeddableOnEmbeddedTest extends BaseEntityManagerFunctionalTestCase {
25-
26-
@Override
27-
protected Class<?>[] getAnnotatedClasses() {
28-
return new Class<?>[] {
29-
City.class,
30-
};
31-
}
32-
23+
@SuppressWarnings("JUnitMalformedDeclaration")
24+
@DomainModel( annotatedClasses = TargetEmbeddableOnEmbeddedTest.City.class )
25+
@SessionFactory
26+
public class TargetEmbeddableOnEmbeddedTest {
3327
@Test
34-
public void testLifecycle() {
35-
//tag::embeddable-Target-persist-example[]
36-
doInJPA(this::entityManagerFactory, entityManager -> {
37-
28+
public void testLifecycle(SessionFactoryScope factoryScope) {
29+
factoryScope.inTransaction( (session) -> {
3830
City cluj = new City();
39-
cluj.setName("Cluj");
40-
cluj.setCoordinates(new GPS(46.77120, 23.62360));
41-
42-
entityManager.persist(cluj);
43-
});
44-
//end::embeddable-Target-persist-example[]
45-
46-
47-
//tag::embeddable-Target-fetching-example[]
48-
doInJPA(this::entityManagerFactory, entityManager -> {
49-
50-
City cluj = entityManager.find(City.class, 1L);
51-
52-
assertEquals(46.77120, cluj.getCoordinates().x(), 0.00001);
53-
assertEquals(23.62360, cluj.getCoordinates().y(), 0.00001);
54-
});
55-
//end::embeddable-Target-fetching-example[]
31+
cluj.setName( "Cluj" );
32+
cluj.setCoordinates( new GPS(46.77120, 23.62360 ) );
33+
34+
session.persist( cluj );
35+
} );
36+
37+
factoryScope.inTransaction( (session) -> {
38+
//tag::embeddable-Target-fetching-example[]
39+
City city = session.find(City.class, 1L);
40+
assert city.getCoordinates() instanceof GPS;
41+
//end::embeddable-Target-fetching-example[]
42+
assertEquals(46.77120, city.getCoordinates().x(), 0.00001);
43+
assertEquals(23.62360, city.getCoordinates().y(), 0.00001);
44+
} );
5645
}
5746

58-
//tag::embeddable-Target-example[]
47+
//tag::embeddable-Target-example[]
5948
public interface Coordinates {
6049
double x();
6150
double y();
6251
}
6352

6453
@Embeddable
6554
public static class GPS implements Coordinates {
55+
// Omitted for brevity
56+
57+
//end::embeddable-Target-example[]
6658

6759
private double latitude;
6860

@@ -85,24 +77,26 @@ public double x() {
8577
public double y() {
8678
return longitude;
8779
}
80+
81+
//tag::embeddable-Target-example[]
8882
}
8983

9084
@Entity(name = "City")
9185
public static class City {
86+
// Omitted for brevity
9287

88+
//end::embeddable-Target-example[]
9389
@Id
9490
@GeneratedValue
9591
private Long id;
9692

9793
private String name;
9894

95+
//tag::embeddable-Target-example[]
9996
@Embedded
10097
@TargetEmbeddable(GPS.class)
10198
private Coordinates coordinates;
102-
103-
//Getters and setters omitted for brevity
104-
105-
//end::embeddable-Target-example[]
99+
//end::embeddable-Target-example[]
106100

107101
public Long getId() {
108102
return id;
@@ -123,7 +117,7 @@ public Coordinates getCoordinates() {
123117
public void setCoordinates(Coordinates coordinates) {
124118
this.coordinates = coordinates;
125119
}
126-
//tag::embeddable-Target-example[]
120+
//tag::embeddable-Target-example[]
127121
}
128-
//end::embeddable-Target-example[]
122+
//end::embeddable-Target-example[]
129123
}

0 commit comments

Comments
 (0)