Queries JPA (EclipseLink) com uma coluna de banco de dados não mapeada
A ideia principal por trás do JPA é o mapeamento objeto-relacional, graças ao qual podemos esquecer as colunas do banco de dados ao criar queries e trabalhar com propriedades de objetos mapeados. No entanto, se quisermos nos referir a uma coluna que não está mapeada, dependendo da forma como construímos a query, pode ser necessário usar uma interface fornecida pela implementação específica do JPA, como o EclipseLink.
Por exemplo, vamos pegar uma tabela de usuários simples:
CREATE TABLE users
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
hidden BOOLEAN DEFAULT false,
name VARCHAR(255),
PRIMARY KEY (id)
);
INSERT INTO users (name, hidden) VALUES ('Adam', true);
INSERT INTO users (name, hidden) VALUES ('Damian', false);
INSERT INTO users (name, hidden) VALUES ('Emma', true);
INSERT INTO users (name, hidden) VALUES ('Alice', false);
Na definição da classe da entidade, eu omito intencionalmente o mapeamento para a coluna hidden
:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Getter
@Setter
@ToString(exclude = "id")
@Entity
@Table(name="users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Agora, suponha que queiramos construir uma query com base na coluna oculta. Podemos começar a construção da query com:
- A interface JPA CriteriaBuilder, que permite referência através de campos mapeados. Para construir uma referência direta à coluna, você precisa usar a interface de uma implementação JPA. No caso do EclipseLink, os passos seriam:
- fazer um downcast do CriteriaBuilder para JpaCriteriaBuilder;
- converter a referência da entidade para a
org.eclipse.persistence.expressions.Expression
específica do EL, criando a referência da coluna viagetField()
e convertendo-a de volta para a forma compatível com JPA; - adicionar a condição relevante;
- A sintaxe JPQL – como acima – podemos usar a sintaxe específica do EL:
WHERE SQL('hidden = true')
; - Uma query nativa.
import org.eclipse.persistence.jpa.JpaCriteriaBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import java.util.List;
@SpringBootTest
class DemoApplicationTests {
@PersistenceContext
EntityManager em;
@Test
void testUnmappedFieldCriteria() {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
List<User> users = em.createQuery(criteria).getResultList();
System.out.println("All users: " + users);
// #1
JpaCriteriaBuilder jpaBuilder = (JpaCriteriaBuilder) builder;
Expression<Boolean> hiddenField = jpaBuilder.fromExpression(
jpaBuilder.toExpression(criteria.from(User.class)).getField("hidden"),
Boolean.class
);
users = em.createQuery(criteria.where(builder.equal(hiddenField, false)))
.getResultList();
System.out.println("#1 Visible users using JpaCriteriaBuilder: " + users);
// #2
users = em.createQuery("SELECT u FROM User u WHERE SQL('hidden = true')", User.class)
.getResultList();
System.out.println("#2 Hidden users using JPQL (EL-flavored): " + users);
// #3
users = em.createNativeQuery("SELECT * FROM users WHERE hidden = true", User.class)
.getResultList();
System.out.println("#3 Hidden users using native query: " + users);
}
}
Em todos os casos mostrados, o EclipseLink gera a query correta, retornando os resultados esperados:
[EL Fine]: sql: 2022-01-09 13:51:28.215--ServerSession(2027837674)--Connection(1139915666)--Thread(Thread[main,5,main])--SELECT ID, NAME FROM users
All users: [User(name=Adam), User(name=Damian), User(name=Emma), User(name=Alice)]
[EL Fine]: sql: 2022-01-09 13:51:28.258--ServerSession(2027837674)--Connection(1139915666)--Thread(Thread[main,5,main])--SELECT ID, NAME FROM users WHERE (hidden = ?)
bind => [false]
#1 Visible users using JpaCriteriaBuilder: [User(name=Damian), User(name=Alice)]
[EL Fine]: sql: 2022-01-09 13:51:28.616--ServerSession(2027837674)--Connection(1139915666)--Thread(Thread[main,5,main])--SELECT ID, NAME FROM users WHERE hidden = true
#2 Hidden users using JPQL (EL-flavored): [User(name=Adam), User(name=Emma)]
[EL Fine]: sql: 2022-01-09 13:51:28.634--ServerSession(2027837674)--Connection(1139915666)--Thread(Thread[main,5,main])--SELECT * FROM users WHERE hidden = true
#3 Hidden users using native query: [User(name=Adam), User(name=Emma)]
Para outras implementações de JPA, você pode encontrar interfaces semelhantes. Por exemplo, o Hibernate oferece os métodos Restrictions.sqlRestriction durante a construção dos critérios que podem ser usados para alcançar o mesmo resultado.