Ponieważ jest to bardzo częste pytanie, napisałem ten artykuł , na którym opiera się ta odpowiedź.
Załóżmy, że nasza aplikacja wykorzystuje następujące Post
, PostComment
, PostDetails
oraz Tag
podmioty, które tworzą jeden-do-wielu, jeden-na-jeden, a wiele do wielu relacji tabeli :
Jak wygenerować metamodel kryteriów JPA
hibernate-jpamodelgen
Narzędzie dostarczane przez Hibernate ORM może być używany do skanowania jednostek projektowych i generowanie kryteria WZP metamodel. Wszystko, co musisz zrobić, to dodać następujące elementy annotationProcessorPath
do pliku konfiguracyjnego maven-compiler-plugin
Mavena pom.xml
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${hibernate.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
Teraz, gdy projekt jest kompilowany, możesz zobaczyć, że w target
folderze generowane są następujące klasy Java:
> tree target/generated-sources/
target/generated-sources/
└── annotations
└── com
└── vladmihalcea
└── book
└── hpjp
└── hibernate
├── forum
│ ├── PostComment_.java
│ ├── PostDetails_.java
│ ├── Post_.java
│ └── Tag_.java
Oznacz encję Metamodel
Jeśli Tag
jednostka jest mapowana w następujący sposób:
@Entity
@Table(name = "tag")
public class Tag {
@Id
private Long id;
private String name;
}
Tag_
Klasa Metamodel generowany jest tak:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Tag.class)
public abstract class Tag_ {
public static volatile SingularAttribute<Tag, String> name;
public static volatile SingularAttribute<Tag, Long> id;
public static final String NAME = "name";
public static final String ID = "id";
}
SingularAttribute
Służy do podstawowych id
i name
Tag
WZP atrybutów jednostki.
Opublikuj Metamodel encji
Post
Jednostka jest odwzorowany tak:
@Entity
@Table(name = "post")
public class Post {
@Id
private Long id;
private String title;
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
@OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
@LazyToOne(LazyToOneOption.NO_PROXY)
private PostDetails details;
@ManyToMany
@JoinTable(
name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private List<Tag> tags = new ArrayList<>();
}
Plik Post
Jednostka ma dwa podstawowe atrybuty, id
i title
, jeden-do-wielu comments
kolekcji, jeden-do-jednego details
stowarzyszenia oraz wiele-do-wielu tags
kolekcji.
Post_
Klasa Metamodel generowany jest w następujący sposób:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Post.class)
public abstract class Post_ {
public static volatile ListAttribute<Post, PostComment> comments;
public static volatile SingularAttribute<Post, PostDetails> details;
public static volatile SingularAttribute<Post, Long> id;
public static volatile SingularAttribute<Post, String> title;
public static volatile ListAttribute<Post, Tag> tags;
public static final String COMMENTS = "comments";
public static final String DETAILS = "details";
public static final String ID = "id";
public static final String TITLE = "title";
public static final String TAGS = "tags";
}
Podstawowe id
i title
atrybuty, a także details
powiązanie jeden do jednego , są reprezentowane przezSingularAttribute
chwilę, comments
a tags
kolekcje i są reprezentowane przez WZP ListAttribute
.
Encja PostDetails Metamodel
Plik PostDetails
Jednostka jest odwzorowany tak:
@Entity
@Table(name = "post_details")
public class PostDetails {
@Id
@GeneratedValue
private Long id;
@Column(name = "created_on")
private Date createdOn;
@Column(name = "created_by")
private String createdBy;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "id")
private Post post;
}
Wszystkie atrybuty encji będą reprezentowane przez JPA SingularAttribute
w powiązanymPostDetails_
klasie Metamodel:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(PostDetails.class)
public abstract class PostDetails_ {
public static volatile SingularAttribute<PostDetails, Post> post;
public static volatile SingularAttribute<PostDetails, String> createdBy;
public static volatile SingularAttribute<PostDetails, Long> id;
public static volatile SingularAttribute<PostDetails, Date> createdOn;
public static final String POST = "post";
public static final String CREATED_BY = "createdBy";
public static final String ID = "id";
public static final String CREATED_ON = "createdOn";
}
Jednostka PostComment Metamodel
PostComment
Są odwzorowywane w sposób następujący:
@Entity
@Table(name = "post_comment")
public class PostComment {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
private String review;
}
Wszystkie atrybuty encji są reprezentowane przez JPA SingularAttribute
w powiązanej PostComments_
klasie Metamodel:
@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(PostComment.class)
public abstract class PostComment_ {
public static volatile SingularAttribute<PostComment, Post> post;
public static volatile SingularAttribute<PostComment, String> review;
public static volatile SingularAttribute<PostComment, Long> id;
public static final String POST = "post";
public static final String REVIEW = "review";
public static final String ID = "id";
}
Korzystanie z metamodelu kryteriów JPA
Bez metamodelu JPA zapytanie Criteria API, które musi pobrać PostComment
jednostki przefiltrowane według powiązanego Post
tytułu, wyglądałoby tak:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<PostComment> query = builder.createQuery(PostComment.class);
Root<PostComment> postComment = query.from(PostComment.class);
Join<PostComment, Post> post = postComment.join("post");
query.where(
builder.equal(
post.get("title"),
"High-Performance Java Persistence"
)
);
List<PostComment> comments = entityManager
.createQuery(query)
.getResultList();
Zauważ, że użyliśmy post
literału String podczas tworzenia Join
wystąpienia i użyliśmy title
literału String podczas odwoływania się doPost
title
.
Metamodel JPA pozwala nam uniknąć zakodowanych na stałe atrybutów encji, co ilustruje poniższy przykład:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<PostComment> query = builder.createQuery(PostComment.class);
Root<PostComment> postComment = query.from(PostComment.class);
Join<PostComment, Post> post = postComment.join(PostComment_.post);
query.where(
builder.equal(
post.get(Post_.title),
"High-Performance Java Persistence"
)
);
List<PostComment> comments = entityManager
.createQuery(query)
.getResultList();
Pisanie zapytań JPA Criteria API jest znacznie łatwiejsze, jeśli używasz narzędzia do uzupełniania kodu, takiego jak Codota. Przeczytaj ten artykuł, aby uzyskać więcej informacji na temat wtyczki Codota IDE.
Lub, powiedzmy, że chcemy pobrać projekcję DTO podczas filtrowania plikówPost
title
iPostDetails
createdOn
atrybuty.
Możemy użyć Metamodelu podczas tworzenia atrybutów złączenia, a także podczas budowania aliasów kolumn projekcji DTO lub podczas odwoływania się do atrybutów encji, które musimy filtrować:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
Root<PostComment> postComment = query.from(PostComment.class);
Join<PostComment, Post> post = postComment.join(PostComment_.post);
query.multiselect(
postComment.get(PostComment_.id).alias(PostComment_.ID),
postComment.get(PostComment_.review).alias(PostComment_.REVIEW),
post.get(Post_.title).alias(Post_.TITLE)
);
query.where(
builder.and(
builder.like(
post.get(Post_.title),
"%Java Persistence%"
),
builder.equal(
post.get(Post_.details).get(PostDetails_.CREATED_BY),
"Vlad Mihalcea"
)
)
);
List<PostCommentSummary> comments = entityManager
.createQuery(query)
.unwrap(Query.class)
.setResultTransformer(Transformers.aliasToBean(PostCommentSummary.class))
.getResultList();
Fajnie, prawda?