Zapytania JPA (EclipseLink) zawierające niezmapowaną kolumnę
Główną ideą stojącą za interfejsem JPA jest mapowanie obiektowo relacyjne, dzięki któremu podczas konstrukcji zapytań zapominamy o kolumnach bazodanowych i operujemy na zmapowanych właściwościach obiektu. Jeśli jednak chcemy odnieść się do kolumny, której nie odwzorowaliśmy, to w zależności od tego, jak budujemy zapytanie, jego zrealizowanie może wymagać skorzystania z interfejsu dostarczanego przez implementację JPA, np. EclipseLinka.
Na tapet weźmy prostą tabelę użytkowników:
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);
W definicji klasy encji celowo pominę mapowanie dla kolumny 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;
}
Zakładając teraz, że chcemy skonstruować zapytanie w oparciu o kolumnę hidden
, możemy wystartować od:
- Interfejsu JPA CriteriaBuilder – pozwala na referencję poprzez zmapowane pola, a w celu skonstruowania bezpośredniego odniesienia do kolumny konieczne będzie skorzystanie z interfejsu implementacji. W przypadku EclipseLink będzie to:
- zrzutowanie klasy CriteriaBuilder na JpaCriteriaBuilder;
- przekształcenie referencji do encji na interfejs
org.eclipse.persistence.expressions.Expression
, utworzenie odniesienie się do kolumy poprzezgetField()
i przekształcenie z powrotem do formy kompatybilnej z JPA; - standardowe dodanie warunku;
- Składni JPQL – podobnie jak wyżej – możemy wykorzystać składnię specyficzną dla EL:
WHERE SQL('hidden = true')
; - Utworzenie zapytania w formie natywnej.
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);
}
}
We wszystkich pokazanych przypadkach EclipseLink wygeneruje właściwe zapytanie i otrzymamy oczekiwane rezultaty:
[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)]
W przypadku innych implementacji JPA możemy znaleźć podobne interfejsy. Przykładowo Hibernate oferuje metodę Restrictions.sqlRestriction podczas konstrukcji kryteriów do uzyskania takiego samego rezultatu.