BAEL-6163 - Add code examples for the article "Querydsl vs. JPA Criteria"

This commit is contained in:
ICKostiantyn.Ivanov 2024-03-11 12:35:51 +01:00
parent 8760727aa0
commit bd5512454d
8 changed files with 546 additions and 6 deletions

View File

@ -15,6 +15,18 @@
</parent>
<dependencies>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>${spring-boot.version}</version>
</dependency>
<!-- QueryDSL -->
<dependency>
<groupId>com.querydsl</groupId>
@ -36,6 +48,16 @@
<version>${hibernate-core.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<dependency>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>5.0</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
@ -103,24 +125,36 @@
<version>${maven-compiler-plugin.version}</version>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<!-- QueryDSL plugin -->
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>${apt-maven-plugin.version}</version>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>5.0</version>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
<processors>
<processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</processors>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
@ -132,6 +166,7 @@
<commons-dbcp.version>1.4</commons-dbcp.version>
<apt-maven-plugin.version>1.1.3</apt-maven-plugin.version>
<hibernate-core.version>6.4.2.Final</hibernate-core.version>
<spring-boot.version>3.2.3</spring-boot.version>
</properties>
</project>

View File

@ -0,0 +1,59 @@
package com.baeldung.querydslvsjpacriteria.entities;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
@Entity
public class GroupUser {
@Id
@GeneratedValue
private Long id;
private String login;
@ManyToMany(mappedBy = "groupUsers", cascade = CascadeType.PERSIST)
private Set<UserGroup> userGroups = new HashSet<>();
@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "groupUser")
private Set<Task> tasks = new HashSet<>(0);
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public Set<UserGroup> getUserGroups() {
return userGroups;
}
public void setUserGroups(Set<UserGroup> userGroups) {
this.userGroups = userGroups;
}
public Set<Task> getTasks() {
return tasks;
}
public void setTasks(Set<Task> tasks) {
this.tasks = tasks;
}
}

View File

@ -0,0 +1,43 @@
package com.baeldung.querydslvsjpacriteria.entities;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
@Entity
public class Task {
@Id
@GeneratedValue
private Long id;
private String description;
@ManyToOne
private GroupUser groupUser;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public GroupUser getUser() {
return groupUser;
}
public void setUser(GroupUser groupUser) {
this.groupUser = groupUser;
}
}

View File

@ -0,0 +1,47 @@
package com.baeldung.querydslvsjpacriteria.entities;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
@Entity
public class UserGroup {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToMany(cascade = CascadeType.PERSIST)
private Set<GroupUser> groupUsers = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}

View File

@ -0,0 +1,25 @@
package com.baeldung.querydslvsjpacriteria.repositories;
import java.util.List;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import com.baeldung.querydslvsjpacriteria.entities.UserGroup;
import com.baeldung.querydslvsjpacriteria.entities.UserGroup_;
public interface UserGroupJpaSpecificationRepository extends JpaRepository<UserGroup, Long>,
JpaSpecificationExecutor<UserGroup> {
default List<UserGroup> findAllWithNameInAnyList(List<String> names1, List<String> names2) {
return findAll(specNameInAnyList(names1, names2));
}
default Specification<UserGroup> specNameInAnyList(List<String> names1, List<String> names2) {
return (root, q, cb) -> cb.or(
root.get(UserGroup_.name).in(names1),
root.get(UserGroup_.name).in(names2)
);
}
}

View File

@ -0,0 +1,27 @@
package com.baeldung.querydslvsjpacriteria.repositories;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import com.baeldung.querydslvsjpacriteria.entities.QUserGroup;
import com.baeldung.querydslvsjpacriteria.entities.UserGroup;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
public interface UserGroupQuerydslPredicateRepository extends JpaRepository<UserGroup, Long>, QuerydslPredicateExecutor<UserGroup> {
default List<UserGroup> findAllWithNameInAnyList(List<String> names1, List<String> names2) {
return StreamSupport
.stream(findAll(predicateInAnyList(names1, names2)).spliterator(), false)
.collect(Collectors.toList());
}
default Predicate predicateInAnyList(List<String> names1, List<String> names2) {
return new BooleanBuilder().and(QUserGroup.userGroup.name.in(names1))
.or(QUserGroup.userGroup.name.in(names2));
}
}

View File

@ -0,0 +1,270 @@
package com.baeldung.querydslvsjpacriteria;
import static com.baeldung.querydslvsjpacriteria.entities.GroupUser_.tasks;
import static com.baeldung.querydslvsjpacriteria.entities.QGroupUser.groupUser;
import static com.baeldung.querydslvsjpacriteria.entities.QTask.task;
import static com.baeldung.querydslvsjpacriteria.entities.QUserGroup.userGroup;
import static com.baeldung.querydslvsjpacriteria.entities.UserGroup_.GROUP_USERS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.baeldung.querydslvsjpacriteria.entities.GroupUser;
import com.baeldung.querydslvsjpacriteria.entities.Task;
import com.baeldung.querydslvsjpacriteria.entities.UserGroup;
import com.baeldung.querydslvsjpacriteria.entities.UserGroup_;
import com.baeldung.querydslvsjpacriteria.repositories.UserGroupJpaSpecificationRepository;
import com.baeldung.querydslvsjpacriteria.repositories.UserGroupQuerydslPredicateRepository;
import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.CriteriaUpdate;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Root;
@EnableJpaRepositories(basePackages = {"com.baeldung.querydslvsjpacriteria.repositories"})
@ContextConfiguration("/test-context.xml")
@ExtendWith({SpringExtension.class, TimingExtension.class})
class QuerydslVSJPACriteriaIntegrationTest {
private static final Logger LOGGER = LoggerFactory.getLogger(QuerydslVSJPACriteriaIntegrationTest.class);
private static EntityManagerFactory emf;
private EntityManager em;
private JPAQueryFactory queryFactory;
@Autowired
private UserGroupJpaSpecificationRepository userGroupJpaSpecificationRepository;
@Autowired
private UserGroupQuerydslPredicateRepository userQuerydslPredicateRepository;
@BeforeAll
static void populateDatabase() {
emf = Persistence.createEntityManagerFactory("com.baeldung.querydsl.intro");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Stream.of("Group 1", "Group 2", "Group 3")
.forEach(g -> {
UserGroup userGroup = new UserGroup();
userGroup.setName(g);
em.persist(userGroup);
IntStream.range(0, 10)
.forEach(u -> {
GroupUser groupUser = new GroupUser();
groupUser.setLogin("User" + u);
groupUser.getUserGroups().add(userGroup);
em.persist(groupUser);
userGroup.getGroupUsers().add(groupUser);
IntStream.range(0, 10000)
.forEach(t -> {
Task task = new Task();
task.setDescription(groupUser.getLogin() + " task #" + t);
task.setUser(groupUser);
em.persist(task);
});
});
em.merge(userGroup);
});
em.getTransaction().commit();
em.close();
}
@BeforeEach
void setUp() {
em = emf.createEntityManager();
em.getTransaction().begin();
queryFactory = new JPAQueryFactory(em);
createUserGroup("Group 1");
createUserGroup("Group 4");
}
@Test
void givenJpaCriteria_whenGetAllTheUserGroups_thenExpectedNumberOfItemsShouldBePresent() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<UserGroup> cr = cb.createQuery(UserGroup.class);
Root<UserGroup> root = cr.from(UserGroup.class);
CriteriaQuery<UserGroup> select = cr.select(root);
TypedQuery<UserGroup> query = em.createQuery(select);
List<UserGroup> results = query.getResultList();
assertEquals(3, results.size());
}
@Test
void givenQueryDSL_whenGetAllTheUserGroups_thenExpectedNumberOfItemsShouldBePresent() {
List<UserGroup> results = queryFactory.selectFrom(userGroup).fetch();
assertEquals(3, results.size());
}
@Test
void givenJpaCriteria_whenGetTheUserGroups_thenExpectedAggregatedDataShouldBePresent() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> cr = cb.createQuery(Object[].class);
Root<UserGroup> root = cr.from(UserGroup.class);
CriteriaQuery<Object[]> select = cr
.multiselect(root.get(UserGroup_.name), cb.countDistinct(root.get(UserGroup_.id)))
.where(cb.or(
root.get(UserGroup_.name).in("Group 1", "Group 2"),
root.get(UserGroup_.name).in("Group 4", "Group 5")
))
.orderBy(cb.desc(root.get(UserGroup_.name)))
.groupBy(root.get(UserGroup_.name));
TypedQuery<Object[]> query = em.createQuery(select);
List<Object[]> results = query.getResultList();
assertEquals(2, results.size());
assertEquals("Group 2", results.get(0)[0]);
assertEquals(1L, results.get(0)[1]);
assertEquals("Group 1", results.get(1)[0]);
assertEquals(1L, results.get(1)[1]);
}
@Test
void givenQueryDSL_whenGetTheUserGroups_thenExpectedAggregatedDataShouldBePresent() {
List<Tuple> results = queryFactory
.select(userGroup.name, userGroup.id.countDistinct())
.from(userGroup)
.where(userGroup.name.in("Group 1", "Group 2")
.or(userGroup.name.in("Group 4", "Group 5")))
.orderBy(userGroup.name.desc())
.groupBy(userGroup.name)
.fetch();
assertEquals(2, results.size());
assertEquals("Group 2", results.get(0).get(userGroup.name));
assertEquals(1L, results.get(0).get(userGroup.id.countDistinct()));
assertEquals("Group 1", results.get(1).get(userGroup.name));
assertEquals(1L, results.get(1).get(userGroup.id.countDistinct()));
}
@Test
void givenJpaCriteria_whenGetTheUserGroupsWithJoins_thenExpectedDataShouldBePresent() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<UserGroup> query = cb.createQuery(UserGroup.class);
query.from(UserGroup.class)
.<UserGroup, GroupUser>join(GROUP_USERS, JoinType.LEFT)
.join(tasks, JoinType.LEFT);
List<UserGroup> result = em.createQuery(query).getResultList();
assertUserGroups(result);
}
private void assertUserGroups(List<UserGroup> userGroups) {
assertEquals(3, userGroups.size());
for (UserGroup group : userGroups) {
assertEquals(10, group.getGroupUsers().size());
for (GroupUser user : group.getGroupUsers()) {
assertEquals(10000, user.getTasks().size());
}
}
}
@Test
void givenQueryDSL_whenGetTheUserGroupsWithJoins_thenExpectedDataShouldBePresent() {
List<UserGroup> result = queryFactory
.selectFrom(userGroup)
.leftJoin(userGroup.groupUsers, groupUser)
.leftJoin(groupUser.tasks, task)
.fetch();
assertUserGroups(result);
}
@Test
void givenJpaCriteria_whenModifyTheUserGroup_thenNameShouldBeUpdated() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaUpdate<UserGroup> criteriaUpdate = cb.createCriteriaUpdate(UserGroup.class);
Root<UserGroup> root = criteriaUpdate.from(UserGroup.class);
criteriaUpdate.set(UserGroup_.name, "Group 1 Updated using Jpa Criteria");
criteriaUpdate.where(cb.equal(root.get(UserGroup_.name), "Group 1"));
em.createQuery(criteriaUpdate).executeUpdate();
UserGroup foundGroup = em.find(UserGroup.class, 1L);
assertEquals("Group 1 Updated using Jpa Criteria", foundGroup.getName());
renameEntityBack(foundGroup, "Group 1");
}
private void renameEntityBack(UserGroup foundGroup, String name) {
foundGroup.setName(name);
em.merge(foundGroup);
}
@Test
void givenQueryDSL_whenModifyTheUserGroup_thenNameShouldBeUpdated() {
queryFactory.update(userGroup)
.set(userGroup.name, "Group 1 Updated Using QueryDSL")
.where(userGroup.name.eq("Group 1"))
.execute();
UserGroup foundGroup = em.find(UserGroup.class, 1L);
assertEquals("Group 1 Updated Using QueryDSL", foundGroup.getName());
renameEntityBack(foundGroup, "Group 1");
}
@Test
void givenJpaSpecificationRepository_whenGetTheUserGroups_thenExpectedDataShouldBePresent() {
List<UserGroup> results = userGroupJpaSpecificationRepository.findAllWithNameInAnyList(
List.of("Group 1", "Group 2"), List.of("Group 4", "Group 5"));
assertEquals(2, results.size());
assertEquals("Group 1", results.get(0).getName());
assertEquals("Group 4", results.get(1).getName());
}
@Test
void givenQuerydslPredicateRepository_whenGetTheUserGroups_thenExpectedDataShouldBePresent() {
List<UserGroup> results = userQuerydslPredicateRepository.findAllWithNameInAnyList(
List.of("Group 1", "Group 2"), List.of("Group 4", "Group 5"));
assertEquals(2, results.size());
assertEquals("Group 1", results.get(0).getName());
assertEquals("Group 4", results.get(1).getName());
}
private void createUserGroup(String name) {
UserGroup entity = new UserGroup();
entity.setName(name);
userGroupJpaSpecificationRepository.save(entity);
}
@AfterEach
void tearDown() {
em.getTransaction().commit();
em.close();
userGroupJpaSpecificationRepository.deleteAll();
}
@AfterAll
static void afterClass() {
emf.close();
}
}

View File

@ -0,0 +1,34 @@
package com.baeldung.querydslvsjpacriteria;
import java.lang.reflect.Method;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
private static final Logger logger = LoggerFactory.getLogger(TimingExtension.class);
private static final String START_TIME = "start time";
@Override
public void beforeTestExecution(ExtensionContext context) {
getStore(context).put(START_TIME, System.currentTimeMillis());
}
@Override
public void afterTestExecution(ExtensionContext context) {
Method testMethod = context.getRequiredTestMethod();
long startTime = getStore(context).remove(START_TIME, long.class);
long duration = System.currentTimeMillis() - startTime;
logger.info(String.format("Method [%s] took %s ms.", testMethod.getName(), duration));
}
private Store getStore(ExtensionContext context) {
return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod()));
}
}