Dado que esta es una pregunta muy común, escribí este artículo , en el que se basa esta respuesta.
Asumamos nuestra aplicación utiliza la siguiente Post
, PostComment
, PostDetails
, y Tag
entidades, que forman un uno-a-muchos, uno-a-uno, y muchos-a-muchos relaciones de la tabla :
Cómo generar el metamodelo de criterios JPA
La hibernate-jpamodelgen
herramienta proporcionada por Hibernate ORM se puede utilizar para escanear las entidades del proyecto y generar el metamodelo de criterios JPA. Todo lo que necesita hacer es agregar lo siguiente annotationProcessorPath
al archivo de configuración de maven-compiler-plugin
Maven 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>
Ahora, cuando se compila el proyecto, puede ver que en la target
carpeta, se generan las siguientes clases de Java:
> tree target/generated-sources/
target/generated-sources/
└── annotations
└── com
└── vladmihalcea
└── book
└── hpjp
└── hibernate
├── forum
│ ├── PostComment_.java
│ ├── PostDetails_.java
│ ├── Post_.java
│ └── Tag_.java
Metamodelo de entidad de etiqueta
Si la Tag
entidad se asigna de la siguiente manera:
@Entity
@Table(name = "tag")
public class Tag {
@Id
private Long id;
private String name;
//Getters and setters omitted for brevity
}
La Tag_
clase Metamodel se genera así:
@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";
}
Se SingularAttribute
utiliza para los atributos de entidad básicos id
y name
Tag
JPA.
Publicar metamodelo de entidad
La Post
entidad se asigna así:
@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<>();
//Getters and setters omitted for brevity
}
La Post
entidad tiene dos atributos básicos id
y title
una comments
colección de uno a muchos , una details
asociación de uno a uno y una tags
colección de muchos a muchos .
La Post_
clase Metamodel se genera de la siguiente manera:
@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";
}
Los atributos básicos id
y title
, así como la details
asociación uno a uno , están representados por un SingularAttribute
tiempo, las colecciones comments
y tags
están representadas por el JPA ListAttribute
.
Metamodelo de entidad PostDetails
La PostDetails
entidad se asigna así:
@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;
//Getters and setters omitted for brevity
}
Todos los atributos de la entidad serán representados por el JPA SingularAttribute
en la PostDetails_
clase Metamodel asociada :
@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";
}
Entidad PostComment Metamodel
El PostComment
se asigna de la siguiente manera:
@Entity
@Table(name = "post_comment")
public class PostComment {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
private String review;
//Getters and setters omitted for brevity
}
Y todos los atributos de la entidad están representados por el JPA SingularAttribute
en la PostComments_
clase Metamodel asociada :
@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";
}
Uso del metamodelo de criterios JPA
Sin el metamodelo JPA, una consulta de la API de criterios que necesita buscar las PostComment
entidades filtradas por su Post
título asociado se vería así:
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();
Observe que usamos el post
literal String al crear la Join
instancia, y usamos el title
literal String al hacer referencia al Post
title
.
El metamodelo JPA nos permite evitar atributos de entidad de codificación rígida, como se ilustra en el siguiente ejemplo:
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();
Escribir consultas de API de criterios de JPA es mucho más fácil si está utilizando una herramienta de finalización de código como Codota. Consulte este artículo para obtener más detalles sobre el complemento IDE de Codota.
O digamos que queremos obtener una proyección DTO mientras filtramos Post
title
los PostDetails
createdOn
atributos y.
Podemos usar el Metamodelo al crear los atributos de unión, así como al construir los alias de la columna de proyección DTO o al hacer referencia a los atributos de entidad que necesitamos filtrar:
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();
¿Guay, verdad?