Bael 7452 updated (#15714)

* BAEL-7452: Lazy loaded lists

* BAEL-7452: Example with Lazy Lists

* BAEL-7452: Test for lazy groups
This commit is contained in:
Eugene Kovko 2024-01-23 19:23:39 +01:00 committed by GitHub
parent 69fad866d9
commit 15de11de9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 586 additions and 0 deletions

View File

@ -0,0 +1,8 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

@ -0,0 +1,22 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import java.util.Set;
import lombok.Data;
@Data
@Entity(name = "interest_group")
@Table(name = "interest_group")
public class Group {
@Id
private Long id;
private String name;
@ManyToMany
private Set<User> members;
}

View File

@ -0,0 +1,7 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface GroupRepository extends JpaRepository<Group, Long> {
}

View File

@ -0,0 +1,29 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class GroupService {
private final GroupRepository groupRepository;
@Autowired
public GroupService(GroupRepository groupRepository) {
this.groupRepository = groupRepository;
}
public Optional<Group> findById(Long aLong) {
return groupRepository.findById(aLong);
}
public List<Group> findAll() {
return groupRepository.findAll();
}
public void save(Group group) {
groupRepository.save(group);
}
}

View File

@ -0,0 +1,43 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import java.util.Objects;
import lombok.Data;
@Entity
@Data
public class Post {
@Id
private Long id;
@Lob
private String content;
@JsonBackReference
@ManyToOne
private User author;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Post post = (Post) o;
return Objects.equals(id, post.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}

View File

@ -0,0 +1,7 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}

View File

@ -0,0 +1,45 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.Objects;
import java.util.Set;
import lombok.Data;
@Data
@Entity(name = "simple_user")
@Table(name = "simple_user")
public class User {
@Id
private Long id;
private String username;
private String email;
@JsonManagedReference
@OneToMany(cascade = CascadeType.ALL, mappedBy = "author")
protected Set<Post> posts;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}

View File

@ -0,0 +1,7 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}

View File

@ -0,0 +1,20 @@
package com.baeldung.listvsset.eager.set.lazy.moderatedomain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class UserService extends com.baeldung.listvsset.Service<User> {
public UserService(JpaRepository<User, Long> repository) {
super(repository);
}
@Override
public UserRepository getRepository() {
return ((UserRepository) super.getRepository());
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

@ -0,0 +1,22 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import java.util.List;
import lombok.Data;
@Data
@Entity(name = "interest_group")
@Table(name = "interest_group")
public class Group {
@Id
private Long id;
private String name;
@ManyToMany
private List<User> members;
}

View File

@ -0,0 +1,7 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface GroupRepository extends JpaRepository<Group, Long> {
}

View File

@ -0,0 +1,35 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import java.util.List;
import java.util.Optional;
import java.util.function.ToIntFunction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class GroupService {
private final GroupRepository groupRepository;
@Autowired
public GroupService(GroupRepository groupRepository) {
this.groupRepository = groupRepository;
}
public Optional<Group> findById(Long aLong) {
return groupRepository.findById(aLong);
}
public List<Group> findAll() {
return groupRepository.findAll();
}
public int countNumberOfRequestsWithFunction(ToIntFunction<List<Group>> function) {
return function.applyAsInt(groupRepository.findAll());
}
public <S extends Group> S save(S entity) {
return groupRepository.save(entity);
}
}

View File

@ -0,0 +1,23 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import lombok.Data;
@Entity
@Data
public class Post {
@Id
private Long id;
@Lob
private String content;
@JsonBackReference
@ManyToOne
private User author;
}

View File

@ -0,0 +1,7 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}

View File

@ -0,0 +1,45 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.List;
import java.util.Objects;
import lombok.Data;
@Data
@Entity(name = "simple_user")
@Table(name = "simple_user")
public class User {
@Id
private Long id;
private String username;
private String email;
@JsonManagedReference
@OneToMany(cascade = CascadeType.ALL, mappedBy = "author")
protected List<Post> posts;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}

View File

@ -0,0 +1,7 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}

View File

@ -0,0 +1,20 @@
package com.baeldung.listvsset.lazy.list.moderatedomain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class UserService extends com.baeldung.listvsset.Service<User> {
public UserService(JpaRepository<User, Long> repository) {
super(repository);
}
@Override
public UserRepository getRepository() {
return ((UserRepository) super.getRepository());
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.nplusone.defaultfetch.list;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

@ -0,0 +1,23 @@
package com.baeldung.nplusone.defaultfetch.list;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import lombok.Data;
@Entity
@Data
public class Post {
@Id
private Long id;
@Lob
private String content;
@JsonBackReference
@ManyToOne
private User author;
}

View File

@ -0,0 +1,7 @@
package com.baeldung.nplusone.defaultfetch.list;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}

View File

@ -0,0 +1,25 @@
package com.baeldung.nplusone.defaultfetch.list;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.List;
import lombok.Data;
@Data
@Entity(name = "simple_user")
@Table(name = "simple_user")
public class User {
@Id
private Long id;
private String username;
private String email;
@JsonManagedReference
@OneToMany(cascade = CascadeType.ALL, mappedBy = "author")
protected List<Post> posts;
}

View File

@ -0,0 +1,9 @@
package com.baeldung.nplusone.defaultfetch.list;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.jpa.repository.JpaRepository;
@Lazy(value = false)
public interface UserRepository extends JpaRepository<User, Long> {
}

View File

@ -0,0 +1,20 @@
package com.baeldung.nplusone.defaultfetch.list;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class UserService extends com.baeldung.listvsset.Service<User> {
public UserService(JpaRepository<User, Long> repository) {
super(repository);
}
@Override
public UserRepository getRepository() {
return ((UserRepository) super.getRepository());
}
}

View File

@ -0,0 +1,77 @@
package com.baeldung.listvsset.lazy.list;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertSelectCount;
import static org.assertj.core.api.Assertions.assertThat;
import com.baeldung.listvsset.BaseNPlusOneIntegrationTest;
import com.baeldung.listvsset.lazy.list.moderatedomain.Application;
import com.baeldung.listvsset.lazy.list.moderatedomain.Group;
import com.baeldung.listvsset.lazy.list.moderatedomain.GroupService;
import com.baeldung.listvsset.lazy.list.moderatedomain.User;
import com.baeldung.listvsset.util.TestConfig;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = {Application.class, TestConfig.class}, properties = {
"hibernate.show_sql=true",
"logging.level.org.hibernate.SQL=debug",
"logging.level.org.hibernate.orm.jdbc.bind=trace"
})
class NPlusOneLazyModerateDomainIntegrationTest extends BaseNPlusOneIntegrationTest<User> {
@Autowired
private GroupService groupService;
@Test
void givenLazyListBasedUser_whenFetchingAllUsers_thenIssueOneRequest() {
getService().findAll();
assertSelectCount(1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenLazyListBasedUser_whenFetchingOneUser_thenIssueOneRequest(Long id) {
getService().getUserById(id);
assertSelectCount(1);
}
@Test
void givenLazyListBasedGroup_whenFetchingAllGroups_thenIssueOneRequest() {
groupService.findAll();
assertSelectCount(1);
}
@Test
void givenLazyListBasedGroup_whenFilteringGroups_thenIssueNPlusOneRequests() {
int numberOfRequests = groupService.countNumberOfRequestsWithFunction(groups -> {
groups.stream()
.map(Group::getMembers)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
return groups.size();
});
assertSelectCount(numberOfRequests + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenLazyListBasedGroup_whenFetchingAllGroups_thenIssueOneRequest(Long groupId) {
Optional<Group> group = groupService.findById(groupId);
assertThat(group).isPresent();
assertSelectCount(1);
}
protected void addUsers() {
List<User> users = jsonUtils.getUsers(User.class);
databaseUtil.saveAll(users);
List<Group> groups = jsonUtils.getGroupsWithMembers(Group.class);
databaseUtil.saveAll(groups);
}
}

View File

@ -0,0 +1,55 @@
package com.baeldung.listvsset.lazy.list;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertSelectCount;
import com.baeldung.listvsset.BaseNPlusOneIntegrationTest;
import com.baeldung.listvsset.util.TestConfig;
import com.baeldung.nplusone.defaultfetch.list.Application;
import com.baeldung.nplusone.defaultfetch.list.Post;
import com.baeldung.nplusone.defaultfetch.list.User;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = {Application.class, TestConfig.class}, properties = {
"hibernate.show_sql=true",
"logging.level.org.hibernate.SQL=debug",
"logging.level.org.hibernate.orm.jdbc.bind=trace"
})
class NPlusOneLazySimpleDomainIntegrationTest extends BaseNPlusOneIntegrationTest<User> {
@Test
void givenLazyListBasedUser_WhenFetchingAllUsers_ThenIssueOneRequests() {
getService().findAll();
assertSelectCount(1);
}
@Test
void givenLazyListBasedUser_WhenFetchingAllUsersCheckingPosts_ThenIssueNPlusOneRequests() {
int numberOfRequests = getService().countNumberOfRequestsWithFunction(users -> {
List<List<Post>> usersWithPosts
= users.stream()
.map(User::getPosts)
.filter(List::isEmpty)
.toList();
return users.size();
});
assertSelectCount(numberOfRequests + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenLazyListBasedUser_WhenFetchingOneUser_ThenIssueTwoRequest(Long id) {
getService().getUserByIdWithPredicate(id, user -> !user.getPosts().isEmpty());
assertSelectCount(2);
}
protected void addUsers() {
List<User> users = jsonUtils.getUsers(User.class);
databaseUtil.saveAll(users);
}
}