BAEL-6887: List vs Set in @OneToMany JPA (#15677)

This commit is contained in:
Eugene Kovko 2024-01-18 20:43:05 +01:00 committed by GitHub
parent 9801f1eb7d
commit 9aa37e2746
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 3432 additions and 0 deletions

View File

@ -42,6 +42,26 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-62</artifactId>
<version>${hypersistence-utils.version}</version>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>db-util</artifactId>
<version>${db.util.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
<build>
@ -58,6 +78,10 @@
<junit-jupiter.version>5.9.3</junit-jupiter.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<db.util.version>1.0.7</db.util.version>
<hypersistence-utils.version>3.7.0</hypersistence-utils.version>
<jackson.version>2.16.0</jackson.version>
<lombok.version>1.18.28</lombok.version>
</properties>
</project>

View File

@ -0,0 +1,14 @@
package com.baeldung.listvsset;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.List;
public abstract class ParametrizationAware<T> {
public List<Class<T>> getParametrizationClass() {
ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
return Arrays.stream(type.getActualTypeArguments())
.map(s -> ((Class<T>) s)).toList();
}
}

View File

@ -0,0 +1,63 @@
package com.baeldung.listvsset;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class Service<S> extends ParametrizationAware<S> {
private final JpaRepository<S, Long> repository;
public Service(JpaRepository<S, Long> repository) {
this.repository = repository;
}
public JpaRepository<S, Long> getRepository() {
return repository;
}
public int countNumberOfRequestsWithFunction(ToIntFunction<List<S>> function) {
return function.applyAsInt(repository.findAll());
}
public Optional<S> getUserById(Long id) {
return repository.findById(id);
}
public void deleteAll() {
repository.deleteAll();
}
public List<S> saveAll(Iterable<S> entities) {
return repository.saveAll(entities);
}
public List<S> findAll() {
return repository.findAll();
}
public Optional<S> getUserByIdWithPredicate(long id, Predicate<S> predicate) {
Optional<S> user = repository.findById(id);
user.ifPresent(predicate::test);
return user;
}
public int getUserByIdWithFunction(Long id, ToIntFunction<S> function) {
Optional<S> optionalUser = repository.findById(id);
if(optionalUser.isPresent()) {
return function.applyAsInt(optionalUser.get());
} else {
return 0;
}
}
public void save(S entity) {
repository.save(entity);
}
}

View File

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

View File

@ -0,0 +1,25 @@
package com.baeldung.listvsset.eager.list.fulldomain;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Data;
@Data
@Entity
public class Comment {
@Id
private Long id;
private String text;
@ManyToOne
private User author;
@JsonBackReference
@ManyToOne
private Post post;
}

View File

@ -0,0 +1,23 @@
package com.baeldung.listvsset.eager.list.fulldomain;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
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(fetch = FetchType.EAGER)
private List<User> members;
}

View File

@ -0,0 +1,32 @@
package com.baeldung.listvsset.eager.list.fulldomain;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.util.List;
import lombok.Data;
@Entity
@Data
public class Post {
@Id
private Long id;
@Lob
private String content;
@JsonManagedReference
@OneToMany(cascade = CascadeType.ALL, mappedBy = "post", fetch = FetchType.EAGER)
private List<Comment> comments;
@JsonBackReference
@ManyToOne
private User author;
}

View File

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

View File

@ -0,0 +1,28 @@
package com.baeldung.listvsset.eager.list.fulldomain;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Lob;
import jakarta.persistence.OneToOne;
import lombok.Data;
@Entity
@Data
public class Profile {
@Id
private Long id;
@Lob
private String biography;
private String website;
private String profilePictureUrl;
@JsonBackReference
@OneToOne(mappedBy = "profile")
@JoinColumn(unique = true)
private User user;
}

View File

@ -0,0 +1,36 @@
package com.baeldung.listvsset.eager.list.fulldomain;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
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
@OneToOne(cascade = CascadeType.ALL)
private Profile profile;
@JsonManagedReference
@OneToMany(cascade = CascadeType.ALL, mappedBy = "author", fetch = FetchType.EAGER)
protected List<Post> posts;
@ManyToMany(mappedBy = "members", fetch = FetchType.EAGER)
private List<Group> groups;
}

View File

@ -0,0 +1,7 @@
package com.baeldung.listvsset.eager.list.fulldomain;
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.list.fulldomain;
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.eager.list.moderatedomain;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

@ -0,0 +1,23 @@
package com.baeldung.listvsset.eager.list.moderatedomain;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
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(fetch = FetchType.EAGER)
private List<User> members;
}

View File

@ -0,0 +1,7 @@
package com.baeldung.listvsset.eager.list.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.list.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 <S extends Group> S save(S entity) {
return groupRepository.save(entity);
}
}

View File

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

View File

@ -0,0 +1,46 @@
package com.baeldung.listvsset.eager.list.moderatedomain;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
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", fetch = FetchType.EAGER)
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.eager.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.eager.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.listvsset.eager.list.simpledomain;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

@ -0,0 +1,23 @@
package com.baeldung.listvsset.eager.list.simpledomain;
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.eager.list.simpledomain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}

View File

@ -0,0 +1,26 @@
package com.baeldung.listvsset.eager.list.simpledomain;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
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", fetch = FetchType.EAGER)
protected List<Post> posts;
}

View File

@ -0,0 +1,9 @@
package com.baeldung.listvsset.eager.list.simpledomain;
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.listvsset.eager.list.simpledomain;
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.eager.set.fulldomain;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

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

View File

@ -0,0 +1,43 @@
package com.baeldung.listvsset.eager.set.fulldomain;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import java.util.Objects;
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(fetch = FetchType.EAGER)
private Set<User> members;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Group group = (Group) o;
return Objects.equals(id, group.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}

View File

@ -0,0 +1,52 @@
package com.baeldung.listvsset.eager.set.fulldomain;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.util.Objects;
import java.util.Set;
import lombok.Data;
@Entity
@Data
public class Post {
@Id
private Long id;
@Lob
private String content;
@JsonManagedReference
@OneToMany(cascade = CascadeType.ALL, mappedBy = "post", fetch = FetchType.EAGER)
private Set<Comment> comments;
@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.fulldomain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}

View File

@ -0,0 +1,28 @@
package com.baeldung.listvsset.eager.set.fulldomain;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Lob;
import jakarta.persistence.OneToOne;
import lombok.Data;
@Entity
@Data
public class Profile {
@Id
private Long id;
@Lob
private String biography;
private String website;
private String profilePictureUrl;
@JsonBackReference
@OneToOne(mappedBy = "profile")
@JoinColumn(unique = true)
private User user;
}

View File

@ -0,0 +1,56 @@
package com.baeldung.listvsset.eager.set.fulldomain;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
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
@OneToOne(cascade = CascadeType.ALL)
private Profile profile;
@JsonManagedReference
@OneToMany(cascade = CascadeType.ALL, mappedBy = "author", fetch = FetchType.EAGER)
protected Set<Post> posts;
@ManyToMany(mappedBy = "members", fetch = FetchType.EAGER)
private Set<Group> groups;
@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.fulldomain;
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.fulldomain;
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.eager.set.moderatedomain;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

@ -0,0 +1,23 @@
package com.baeldung.listvsset.eager.set.moderatedomain;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
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(fetch = FetchType.EAGER)
private Set<User> members;
}

View File

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

View File

@ -0,0 +1,46 @@
package com.baeldung.listvsset.eager.set.moderatedomain;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
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", fetch = FetchType.EAGER)
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.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.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.eager.set.simpledomain;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

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

View File

@ -0,0 +1,27 @@
package com.baeldung.listvsset.eager.set.simpledomain;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
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", fetch = FetchType.EAGER)
protected Set<Post> posts;
}

View File

@ -0,0 +1,9 @@
package com.baeldung.listvsset.eager.set.simpledomain;
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.listvsset.eager.set.simpledomain;
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,76 @@
package com.baeldung.listvsset;
import static org.assertj.core.api.Assertions.assertThat;
import com.baeldung.listvsset.util.DatabaseUtil;
import com.baeldung.listvsset.util.JsonUtils;
import io.hypersistence.utils.jdbc.validator.SQLStatementCountValidator;
import jakarta.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(properties = {
"spring.jpa.generate-ddl=true",
"spring.jpa.show-sql=false",
"spring.datasource.url=jdbc:h2:mem:~/test;DATABASE_TO_UPPER=false"
})
abstract public class BaseNPlusOneIntegrationTest<T> extends ParametrizationAware<T> {
@Autowired
protected List<Service<?>> services;
@Autowired
protected DatabaseUtil databaseUtil;
@Autowired
protected JsonUtils jsonUtils;
private final Map<Class<?>, Service<?>> serviceMap = new HashMap<>();
@PostConstruct
void init() {
for (Service<?> service : services) {
Class<?> parametrization = service.getParametrizationClass().get(0);
serviceMap.put(parametrization, service);
}
}
@BeforeEach
void setUp() {
addUsers();
SQLStatementCountValidator.reset();
System.out.println("************************************************");
System.out.println("\n\n\n\n\n");
}
@AfterEach
void tearDown() {
System.out.println("\n\n\n\n\n");
System.out.println("************************************************");
databaseUtil.truncateAllTables();
SQLStatementCountValidator.reset();
}
@Test
void givenCorrectConfigurationWhenStartContextThenRepositoryIsPresent() {
assertThat(getService()).isNotNull();
}
@Test
void givenCorrectDatabaseWhenStartThenDatabaseIsNotEmpty() {
List<?> result = getService().findAll();
assertThat(result).isNotEmpty();
}
protected Service<T> getService() {
Class<T> parametrization = getParametrizationClass().get(0);
return (Service<T>) serviceMap.get(parametrization);
}
protected abstract void addUsers();
}

View File

@ -0,0 +1,56 @@
package com.baeldung.listvsset;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.baeldung.listvsset.eager.list.fulldomain.Application;
import com.baeldung.listvsset.eager.list.fulldomain.Comment;
import com.baeldung.listvsset.eager.list.fulldomain.Group;
import com.baeldung.listvsset.eager.list.fulldomain.Post;
import com.baeldung.listvsset.eager.list.fulldomain.Profile;
import com.baeldung.listvsset.eager.list.fulldomain.User;
import com.baeldung.listvsset.util.JsonUtils;
import com.baeldung.listvsset.util.TestConfig;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = {Application.class, TestConfig.class})
class JsonUtilTest {
@Autowired
private JsonUtils jsonUtils;
@Test
void givenFileWhenConvertingToUsersThenConversionIsCorrect() {
List<User> users = jsonUtils.getUsers(User.class);
assertThat(users).isNotEmpty();
boolean wentThroughEverything = false;
for (User user : users) {
assertThat(user.getGroups()).isNotNull();
Profile profile = user.getProfile();
if (profile != null) {
assertThat(user.getId()).isEqualTo(profile.getUser().getId());
}
for (Post post : user.getPosts()) {
assertThat(user.getId()).isEqualTo(post.getAuthor().getId());
if (post.getComments() != null) {
wentThroughEverything = true;
for (Comment comment : post.getComments()) {
assertThat(post.getId()).isEqualTo(comment.getPost().getId());
}
}
}
}
assertTrue(wentThroughEverything);
}
@Test
void givenFileWhenConvertingToGroupsThenConversionIsCorrect() {
List<Group> groups = jsonUtils.getGroups(Group.class);
assertThat(groups).isNotEmpty();
}
}

View File

@ -0,0 +1,129 @@
package com.baeldung.listvsset.list;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertSelectCount;
import com.baeldung.listvsset.BaseNPlusOneIntegrationTest;
import com.baeldung.listvsset.eager.list.fulldomain.Application;
import com.baeldung.listvsset.eager.list.fulldomain.Comment;
import com.baeldung.listvsset.eager.list.fulldomain.Group;
import com.baeldung.listvsset.eager.list.fulldomain.Post;
import com.baeldung.listvsset.eager.list.fulldomain.User;
import com.baeldung.listvsset.util.TestConfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.ToIntFunction;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
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 NPlusOneEagerFullDomainIntegrationTest extends BaseNPlusOneIntegrationTest<User> {
public static final String POSTS = "posts";
public static final String USERS = "users";
@ParameterizedTest
@MethodSource
void givenEagerListBasedUser_WhenFetchingAllUsers_ThenIssueNPlusOneRequests(ToIntFunction<List<User>> function) {
int numberOfRequests = getService().countNumberOfRequestsWithFunction(function);
assertSelectCount(numberOfRequests);
}
static Stream<Arguments> givenEagerListBasedUser_WhenFetchingAllUsers_ThenIssueNPlusOneRequests() {
return Stream.of(
Arguments.of((ToIntFunction<List<User>>) s -> {
int result = 2 * s.size() + 1;
List<Post> posts = s.stream().map(User::getPosts)
.flatMap(List::stream)
.toList();
result += posts.size();
return result;
})
);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerListBasedUser_WhenFetchingOneUser_ThenUseDFS(Long id) {
int numberOfRequests = getService().getUserByIdWithFunction(id, this::countNumberOfRequests);
assertSelectCount(numberOfRequests);
}
private int countNumberOfRequests(User user) {
HashMap<String, Set<Long>> visitedMap = new HashMap<>();
visitedMap.put(POSTS, new HashSet<>());
visitedMap.put(USERS, new HashSet<>());
int result = 1;
visitedMap.get(USERS).add(user.getId());
List<Post> posts = user.getPosts();
result += 1;
result += explorePosts(posts, visitedMap);
return result;
}
private int explorePosts(List<Post> posts, HashMap<String, Set<Long>> visitedMap) {
int result = 0;
if (posts == null || posts.isEmpty()) {
return result;
}
for (Post post : posts) {
if (!visitedMap.get(POSTS).contains(post.getId())) {
result++;
visitedMap.get(POSTS).add(post.getId());
List<User> commenters = post.getComments().stream().map(Comment::getAuthor).toList();
result += exploreUsers(commenters, visitedMap);
}
}
return result;
}
private int exploreUsers(List<User> users, HashMap<String, Set<Long>> visitedMap) {
int result = 0;
if (users == null || users.isEmpty()) {
return result;
}
for (User user : users) {
if (!visitedMap.get(USERS).contains(user.getId())) {
++result;
visitedMap.get(USERS).add(user.getId());
result += explorePosts(user.getPosts(), visitedMap);
++result;
}
}
return result;
}
protected void addUsers() {
List<Group> groups = jsonUtils.getGroups(Group.class);
databaseUtil.saveAll(groups);
List<User> users = jsonUtils.getUsers(User.class);
Map<Long, List<Comment>> comments = new HashMap<>();
for (User user : users) {
for (Post post : user.getPosts()) {
if (!comments.containsKey(post.getId())) {
comments.put(post.getId(), new ArrayList<>());
}
comments.get(post.getId()).addAll(post.getComments());
post.setComments(Collections.emptyList());
}
}
databaseUtil.saveAll(users);
// Handle non-existent users while adding comments
databaseUtil.mergeAll(jsonUtils.getUsers(User.class));
}
}

View File

@ -0,0 +1,87 @@
package com.baeldung.listvsset.list;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertDeleteCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertInsertCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertSelectCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.reset;
import static org.assertj.core.api.Assertions.assertThat;
import com.baeldung.listvsset.BaseNPlusOneIntegrationTest;
import com.baeldung.listvsset.eager.list.moderatedomain.Application;
import com.baeldung.listvsset.eager.list.moderatedomain.Group;
import com.baeldung.listvsset.eager.list.moderatedomain.GroupService;
import com.baeldung.listvsset.eager.list.moderatedomain.User;
import com.baeldung.listvsset.util.TestConfig;
import java.util.List;
import java.util.Optional;
import java.util.Set;
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 NPlusOneEagerModerateDomainIntegrationTest extends BaseNPlusOneIntegrationTest<User> {
@Autowired
private GroupService groupService;
@Test
void givenEagerListBasedUser_whenFetchingAllUsers_thenIssueNPlusOneRequests() {
List<User> users = getService().findAll();
assertSelectCount(users.size() + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerListBasedUser_whenFetchingOneUser_thenIssueOneRequest(Long id) {
getService().getUserById(id);
assertSelectCount(1);
}
@Test
void givenEagerListBasedGroup_whenFetchingAllGroups_thenIssueNPlusMPlusOneRequests() {
List<Group> groups = groupService.findAll();
Set<User> users = groups.stream().map(Group::getMembers).flatMap(List::stream).collect(Collectors.toSet());
assertSelectCount(groups.size() + users.size() + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerListBasedGroup_whenFetchingAllGroups_thenIssueNPlusOneRequests(Long groupId) {
Optional<Group> group = groupService.findById(groupId);
assertThat(group).isPresent();
assertSelectCount(1 + group.get().getMembers().size());
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerListBasedGroup_whenRemoveUser_thenRecreateGroup(Long groupId) {
Optional<Group> optionalGroup = groupService.findById(groupId);
assertThat(optionalGroup).isPresent();
Group group = optionalGroup.get();
List<User> members = group.getMembers();
assertSelectCount(1 + members.size());
if (!members.isEmpty()) {
reset();
members.remove(0);
groupService.save(group);
assertSelectCount(1 + members.size() + 1);
assertDeleteCount(1);
assertInsertCount(members.size());
}
}
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,67 @@
package com.baeldung.listvsset.list;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertSelectCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertUpdateCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.reset;
import static org.assertj.core.api.Assertions.assertThat;
import com.baeldung.listvsset.BaseNPlusOneIntegrationTest;
import com.baeldung.listvsset.eager.list.simpledomain.Application;
import com.baeldung.listvsset.eager.list.simpledomain.Post;
import com.baeldung.listvsset.eager.list.simpledomain.User;
import com.baeldung.listvsset.util.TestConfig;
import java.util.List;
import java.util.Optional;
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 NPlusOneEagerSimpleDomainIntegrationTest extends BaseNPlusOneIntegrationTest<User> {
@Test
void givenEagerListBasedUser_WhenFetchingAllUsers_ThenIssueNPlusOneRequests() {
List<User> users = getService().findAll();
assertSelectCount(users.size() + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerListBasedUser_WhenFetchingOneUser_ThenIssueOneRequest(Long id) {
getService().getUserById(id);
assertSelectCount(1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerListBasedUser_whenDeletePost_ThenIssueSingleUpdate(Long id) {
Optional<User> optionalUser = getService().getUserById(id);
assertSelectCount(1);
optionalUser.ifPresent(user -> {
List<Post> posts = user.getPosts();
int originalNumberOfPosts = posts.size();
reset();
if (!posts.isEmpty()) {
posts.get(0).setAuthor(null);
getService().save(user);
assertSelectCount(1);
assertUpdateCount(1);
getService().getUserById(id).ifPresent(updatedUser -> {
assertThat(updatedUser.getPosts()).hasSize(originalNumberOfPosts - 1);
});
}
});
}
protected void addUsers() {
List<User> users = jsonUtils.getUsers(User.class);
databaseUtil.saveAll(users);
}
}

View File

@ -0,0 +1,106 @@
package com.baeldung.listvsset.set;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertSelectCount;
import com.baeldung.listvsset.BaseNPlusOneIntegrationTest;
import com.baeldung.listvsset.eager.set.fulldomain.Application;
import com.baeldung.listvsset.eager.set.fulldomain.Comment;
import com.baeldung.listvsset.eager.set.fulldomain.Group;
import com.baeldung.listvsset.eager.set.fulldomain.Post;
import com.baeldung.listvsset.eager.set.fulldomain.User;
import com.baeldung.listvsset.util.TestConfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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 NPlusOneEagerFullDomainJoinIntegrationTest extends BaseNPlusOneIntegrationTest<User> {
public static final String POSTS = "posts";
public static final String USERS = "users";
@Test
void givenEagerSetBasedUser_WhenFetchingAllUsers_ThenIssueNPlusOneRequests() {
List<User> users = getService().findAll();
assertSelectCount(users.size() + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerUserWhenFetchingOneUserThenIssueNPlusOneRequestsWithCartesianProduct(Long id) {
HashMap<String, Set<Long>> visitedMap = new HashMap<>();
visitedMap.put(POSTS, new HashSet<>());
visitedMap.put(USERS, new HashSet<>());
int numberOfRequests = getService()
.getUserByIdWithFunction(id, user -> {
int result = 1;
visitedMap.get(USERS).add(user.getId());
Set<Post> posts = user.getPosts();
result += explorePosts(posts, visitedMap);
return result;
});
assertSelectCount(numberOfRequests);
}
private int explorePosts(Set<Post> posts, HashMap<String, Set<Long>> visitedMap) {
int result = 0;
if (posts == null || posts.isEmpty()) {
return result;
}
for (Post post : posts) {
if (!visitedMap.get(POSTS).contains(post.getId())) {
visitedMap.get(POSTS).add(post.getId());
List<User> commenters = post.getComments().stream().map(Comment::getAuthor).toList();
result += exploreUsers(commenters, visitedMap);
}
}
return result;
}
private int exploreUsers(List<User> users, HashMap<String, Set<Long>> visitedMap) {
int result = 0;
if (users == null || users.isEmpty()) {
return result;
}
for (User user : users) {
if (!visitedMap.get(USERS).contains(user.getId())) {
++result;
visitedMap.get(USERS).add(user.getId());
result += explorePosts(user.getPosts(), visitedMap);
}
}
return result;
}
protected void addUsers() {
List<Group> groups = jsonUtils.getGroups(Group.class);
databaseUtil.saveAll(groups);
List<User> users = jsonUtils.getUsers(User.class);
Map<Long, List<Comment>> comments = new HashMap<>();
for (User user : users) {
for (Post post : user.getPosts()) {
if (!comments.containsKey(post.getId())) {
comments.put(post.getId(), new ArrayList<>());
}
comments.get(post.getId()).addAll(post.getComments());
post.setComments(Collections.emptySet());
}
}
databaseUtil.saveAll(users);
// Handle non-existent users while adding comments
databaseUtil.mergeAll(jsonUtils.getUsers(User.class));
}
}

View File

@ -0,0 +1,83 @@
package com.baeldung.listvsset.set;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertDeleteCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertSelectCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.reset;
import static org.assertj.core.api.Assertions.assertThat;
import com.baeldung.listvsset.BaseNPlusOneIntegrationTest;
import com.baeldung.listvsset.eager.set.moderatedomain.Application;
import com.baeldung.listvsset.eager.set.moderatedomain.Group;
import com.baeldung.listvsset.eager.set.moderatedomain.GroupService;
import com.baeldung.listvsset.eager.set.moderatedomain.User;
import com.baeldung.listvsset.util.TestConfig;
import java.util.List;
import java.util.Optional;
import java.util.Set;
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 NPlusOneEagerModerateDomainIntegrationTest extends BaseNPlusOneIntegrationTest<User> {
@Autowired
private GroupService groupService;
@Test
void givenEagerSetBasedUser_whenFetchingAllUsers_thenIssueNPlusOneRequests() {
List<User> users = getService().findAll();
assertSelectCount(users.size() + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerSetBasedUser_whenFetchingOneUser_thenIssueOneRequest(Long id) {
getService().getUserById(id);
assertSelectCount(1);
}
@Test
void givenEagerSetBasedGroup_whenFetchingAllGroups_thenIssueNPlusOneRequests() {
List<Group> groups = groupService.findAll();
assertSelectCount(groups.size() + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerSetBasedGroup_whenFetchingAllGroups_thenCreateCartesianProductInOneQuery(Long groupId) {
groupService.findById(groupId);
assertSelectCount(1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerListBasedGroup_whenRemoveUser_thenIssueOnlyOneDelete(Long groupId) {
Optional<Group> optionalGroup = groupService.findById(groupId);
assertThat(optionalGroup).isPresent();
Group group = optionalGroup.get();
if (!group.getMembers().isEmpty()) {
reset();
Set<User> newMembers = group.getMembers().stream().skip(1).collect(Collectors.toSet());
group.setMembers(newMembers);
groupService.save(group);
assertSelectCount(1);
assertDeleteCount(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,68 @@
package com.baeldung.listvsset.set;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertSelectCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.assertUpdateCount;
import static com.vladmihalcea.sql.SQLStatementCountValidator.reset;
import static org.assertj.core.api.Assertions.assertThat;
import com.baeldung.listvsset.BaseNPlusOneIntegrationTest;
import com.baeldung.listvsset.eager.set.simpledomain.Application;
import com.baeldung.listvsset.eager.set.simpledomain.Post;
import com.baeldung.listvsset.eager.set.simpledomain.User;
import com.baeldung.listvsset.util.TestConfig;
import java.util.List;
import java.util.Optional;
import java.util.Set;
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 NPlusOneEagerSimpleDomainIntegrationTest extends BaseNPlusOneIntegrationTest<User> {
@Test
void givenEagerSetBasedUser_WhenFetchingAllUsers_ThenIssueNPlusOneRequests() {
List<User> users = getService().findAll();
assertSelectCount(users.size() + 1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerSetBasedUser_WhenFetchingOneUser_ThenIssueOneRequest(Long id) {
getService().getUserById(id);
assertSelectCount(1);
}
@ParameterizedTest
@ValueSource(longs = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
void givenEagerListBasedUser_whenDeletePost_ThenIssueSingleUpdate(Long id) {
Optional<User> optionalUser = getService().getUserById(id);
assertSelectCount(1);
optionalUser.ifPresent(user -> {
Set<Post> posts = user.getPosts();
int originalNumberOfPosts = posts.size();
reset();
if (!posts.isEmpty()) {
posts.iterator().next().setAuthor(null);
getService().save(user);
assertSelectCount(1);
assertUpdateCount(1);
getService().getUserById(id).ifPresent(updatedUser -> {
assertThat(updatedUser.getPosts()).hasSize(originalNumberOfPosts - 1);
});
}
});
}
protected void addUsers() {
List<User> users = jsonUtils.getUsers(User.class);
databaseUtil.saveAll(users);
}
}

View File

@ -0,0 +1,35 @@
package com.baeldung.listvsset.util;
import io.hypersistence.utils.logging.InlineQueryLogEntryCreator;
import javax.sql.DataSource;
import net.ttddyy.dsproxy.listener.ChainListener;
import net.ttddyy.dsproxy.listener.DataSourceQueryCountListener;
import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class DataSourceWrapper implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DataSource originalDataSource) {
ChainListener listener = new ChainListener();
SLF4JQueryLoggingListener loggingListener = new SLF4JQueryLoggingListener();
loggingListener.setQueryLogEntryCreator(new InlineQueryLogEntryCreator());
listener.addListener(loggingListener);
listener.addListener(new DataSourceQueryCountListener());
return ProxyDataSourceBuilder
.create(originalDataSource)
.name("DS-Proxy")
.listener(listener)
.build();
}
return bean;
}
}

View File

@ -0,0 +1,47 @@
package com.baeldung.listvsset.util;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.util.List;
import org.springframework.stereotype.Component;
@Component
public class DatabaseUtil {
public static final String TRUNCATE_QUERY_FORMAT = "TRUNCATE TABLE %s";
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void truncateAllTables() {
switchForeignKeysOff();
entityManager.createNativeQuery("SHOW TABLES", String.class)
.getResultList()
.forEach(this::truncateTable);
switchForeignKeysOn();
}
@Transactional
public <T> void saveAll(List<T> entities) {
entities.forEach(s -> entityManager.persist(s));
}
@Transactional
public <T> void mergeAll(List<T> entities) {
entities.forEach(s -> entityManager.merge(s));
}
private int truncateTable(Object s) {
return entityManager
.createNativeQuery(TRUNCATE_QUERY_FORMAT.formatted(s)).executeUpdate();
}
private void switchForeignKeysOn() {
entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
}
private void switchForeignKeysOff() {
entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
}
}

View File

@ -0,0 +1,51 @@
package com.baeldung.listvsset.util;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class JsonUtils {
@Value("users.json")
private File userFile;
@Value("groups.json")
private File groupFile;
@Value("groups_with_members.json")
private File groupsWithMembersFile;
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public <T> List<T> getUsers(Class<T> userType) {
return getObjects(userType, userFile);
}
public <T> List<T> getGroups(Class<T> userType) {
return getObjects(userType, groupFile);
}
public <T> List<T> getGroupsWithMembers(Class<T> groupType) {
return getObjects(groupType, groupsWithMembersFile);
}
private <T> List<T> getObjects(Class<T> userType, File userFile1) {
try {
return MAPPER.readValue(userFile1, MAPPER.getTypeFactory()
.constructCollectionType(List.class, userType));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,24 @@
package com.baeldung.listvsset.util;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TestConfig {
@Bean
public DataSourceWrapper dataSourceWrapper() {
return new DataSourceWrapper();
}
@Bean
public DatabaseUtil databaseUtil() {
return new DatabaseUtil();
}
@Bean
JsonUtils jsonUtils() {
return new JsonUtils();
}
}

View File

@ -0,0 +1,402 @@
[
{
"id": 1,
"name": "Bitwolf"
},
{
"id": 2,
"name": "Solarbreeze"
},
{
"id": 3,
"name": "Bamity"
},
{
"id": 4,
"name": "Hatity"
},
{
"id": 5,
"name": "Veribet"
},
{
"id": 6,
"name": "Tampflex"
},
{
"id": 7,
"name": "Zontrax"
},
{
"id": 8,
"name": "Aerified"
},
{
"id": 9,
"name": "Konklab"
},
{
"id": 10,
"name": "Tampflex"
},
{
"id": 11,
"name": "Mat Lam Tam"
},
{
"id": 12,
"name": "Alpha"
},
{
"id": 13,
"name": "Quo Lux"
},
{
"id": 14,
"name": "Kanlam"
},
{
"id": 15,
"name": "Veribet"
},
{
"id": 16,
"name": "Ventosanzap"
},
{
"id": 17,
"name": "Opela"
},
{
"id": 18,
"name": "Alphazap"
},
{
"id": 19,
"name": "Lotlux"
},
{
"id": 20,
"name": "Alpha"
},
{
"id": 21,
"name": "Andalax"
},
{
"id": 22,
"name": "Bitchip"
},
{
"id": 23,
"name": "Cookley"
},
{
"id": 24,
"name": "Konklab"
},
{
"id": 25,
"name": "Konklab"
},
{
"id": 26,
"name": "Lotstring"
},
{
"id": 27,
"name": "Konklux"
},
{
"id": 28,
"name": "Temp"
},
{
"id": 29,
"name": "Voyatouch"
},
{
"id": 30,
"name": "Trippledex"
},
{
"id": 31,
"name": "Sonsing"
},
{
"id": 32,
"name": "Andalax"
},
{
"id": 33,
"name": "Voltsillam"
},
{
"id": 34,
"name": "Stronghold"
},
{
"id": 35,
"name": "Hatity"
},
{
"id": 36,
"name": "Andalax"
},
{
"id": 37,
"name": "Holdlamis"
},
{
"id": 38,
"name": "Bamity"
},
{
"id": 39,
"name": "Voltsillam"
},
{
"id": 40,
"name": "Aerified"
},
{
"id": 41,
"name": "Regrant"
},
{
"id": 42,
"name": "Subin"
},
{
"id": 43,
"name": "Tin"
},
{
"id": 44,
"name": "Voltsillam"
},
{
"id": 45,
"name": "Trippledex"
},
{
"id": 46,
"name": "Solarbreeze"
},
{
"id": 47,
"name": "Treeflex"
},
{
"id": 48,
"name": "Fintone"
},
{
"id": 49,
"name": "Tin"
},
{
"id": 50,
"name": "It"
},
{
"id": 51,
"name": "Y-Solowarm"
},
{
"id": 52,
"name": "Bigtax"
},
{
"id": 53,
"name": "Flowdesk"
},
{
"id": 54,
"name": "Vagram"
},
{
"id": 55,
"name": "Keylex"
},
{
"id": 56,
"name": "Zontrax"
},
{
"id": 57,
"name": "Vagram"
},
{
"id": 58,
"name": "Lotstring"
},
{
"id": 59,
"name": "Cookley"
},
{
"id": 60,
"name": "Zaam-Dox"
},
{
"id": 61,
"name": "Zoolab"
},
{
"id": 62,
"name": "Quo Lux"
},
{
"id": 63,
"name": "Ventosanzap"
},
{
"id": 64,
"name": "Ventosanzap"
},
{
"id": 65,
"name": "Lotstring"
},
{
"id": 66,
"name": "Keylex"
},
{
"id": 67,
"name": "Stim"
},
{
"id": 68,
"name": "Alphazap"
},
{
"id": 69,
"name": "It"
},
{
"id": 70,
"name": "Cardify"
},
{
"id": 71,
"name": "Zamit"
},
{
"id": 72,
"name": "Holdlamis"
},
{
"id": 73,
"name": "Konklab"
},
{
"id": 74,
"name": "Ventosanzap"
},
{
"id": 75,
"name": "Wrapsafe"
},
{
"id": 76,
"name": "Cookley"
},
{
"id": 77,
"name": "Y-find"
},
{
"id": 78,
"name": "Regrant"
},
{
"id": 79,
"name": "Otcom"
},
{
"id": 80,
"name": "Cookley"
},
{
"id": 81,
"name": "Bigtax"
},
{
"id": 82,
"name": "Holdlamis"
},
{
"id": 83,
"name": "Bigtax"
},
{
"id": 84,
"name": "Voltsillam"
},
{
"id": 85,
"name": "Duobam"
},
{
"id": 86,
"name": "Flowdesk"
},
{
"id": 87,
"name": "It"
},
{
"id": 88,
"name": "Hatity"
},
{
"id": 89,
"name": "Zathin"
},
{
"id": 90,
"name": "Hatity"
},
{
"id": 91,
"name": "Cardify"
},
{
"id": 92,
"name": "Konklab"
},
{
"id": 93,
"name": "Treeflex"
},
{
"id": 94,
"name": "Flexidy"
},
{
"id": 95,
"name": "Latlux"
},
{
"id": 96,
"name": "Bitwolf"
},
{
"id": 97,
"name": "Zontrax"
},
{
"id": 98,
"name": "Greenlam"
},
{
"id": 99,
"name": "Asoka"
},
{
"id": 100,
"name": "Ronstring"
}
]

View File

@ -0,0 +1,183 @@
[
{
"id": 1,
"name": "Pacocha-Hills",
"members": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4
}
]
},
{
"id": 2,
"name": "Windler, Wiegand and Bernhard",
"members": []
},
{
"id": 3,
"name": "Flatley-Hilll",
"members": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4
},
{
"id": 5
},
{
"id": 6
},
{
"id": 7
},
{
"id": 8
},
{
"id": 9
}
]
},
{
"id": 4,
"name": "Daugherty and Sons",
"members": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4
},
{
"id": 5
},
{
"id": 6
},
{
"id": 7
}
]
},
{
"id": 5,
"name": "Leannon, Kub and Murazik",
"members": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4
},
{
"id": 5
},
{
"id": 6
}
]
},
{
"id": 6,
"name": "Considine-Koss",
"members": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4
},
{
"id": 5
},
{
"id": 6
},
{
"id": 7
}
]
},
{
"id": 7,
"name": "Russel LLC",
"members": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4
}
]
},
{
"id": 8,
"name": "Wisoky-Grimes",
"members": [
{
"id": 1
},
{
"id": 2
}
]
},
{
"id": 9,
"name": "Larson Inc",
"members": [
{
"id": 1
},
{
"id": 2
}
]
},
{
"id": 10,
"name": "Franecki-Simonis",
"members": []
}
]

View File

@ -0,0 +1,899 @@
[
{
"id": 1,
"username": "ecrumbleholme0",
"email": "pmerritt0@wordpress.org",
"groups": [
{
"id": 70
},
{
"id": 67
},
{
"id": 54
},
{
"id": 41
},
{
"id": 40
},
{
"id": 5
},
{
"id": 82
}
],
"profile": {
"id": 1,
"biography": "Mauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis. Integer aliquet, massa id lobortis convallis, tortor risus dapibus augue, vel accumsan tellus nisi eu orci. Mauris lacinia sapien quis libero.",
"website": "https://engadget.com/adipiscing/lorem/vitae/mattis.jpg?non=in&mattis=porttitor&pulvinar=pede&nulla",
"profilePictureUrl": "http://dummyimage.com/207x100.png/dddddd/000000"
},
"posts": []
},
{
"id": 2,
"username": "moldred1",
"email": "grichardes1@shareasale.com",
"groups": [
{
"id": 39
},
{
"id": 53
},
{
"id": 26
},
{
"id": 70
},
{
"id": 49
},
{
"id": 33
},
{
"id": 64
},
{
"id": 65
},
{
"id": 14
},
{
"id": 10
}
],
"profile": {
"id": 2,
"biography": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin risus. Praesent lectus.",
"website": "http://arizona.edu/urna/ut.html?erat=pretium&volutpat=iaculis&in=diam&congue",
"profilePictureUrl": "http://dummyimage.com/200x100.png/dddddd/000000"
},
"posts": [
{
"id": 1,
"content": "Curabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.",
"comments": [
{
"id": 208446,
"test": {
"id": "Optional empowering attitude"
},
"author": {
"id": 10
}
},
{
"id": 229466,
"test": {
"id": "Customizable multi-tasking encryption"
},
"author": {
"id": 3
}
},
{
"id": 408703,
"test": {
"id": "Re-engineered contextually-based protocol"
},
"author": {
"id": 9
}
},
{
"id": 837433,
"test": {
"id": "Centralized well-modulated pricing structure"
},
"author": {
"id": 8
}
}
]
},
{
"id": 2,
"content": "Donec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.",
"comments": []
},
{
"id": 3,
"content": "Vestibulum quam sapien, varius ut, blandit non, interdum in, ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis faucibus accumsan odio. Curabitur convallis.\n\nDuis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus.\n\nMauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis. Integer aliquet, massa id lobortis convallis, tortor risus dapibus augue, vel accumsan tellus nisi eu orci. Mauris lacinia sapien quis libero.",
"comments": [
{
"id": 53333,
"test": {
"id": "Cross-platform bi-directional encoding"
},
"author": {
"id": 6
}
},
{
"id": 806260,
"test": {
"id": "Expanded composite neural-net"
},
"author": {
"id": 1
}
},
{
"id": 266068,
"test": {
"id": "Implemented attitude-oriented knowledge user"
},
"author": {
"id": 3
}
}
]
}
]
},
{
"id": 3,
"username": "agoodsall2",
"email": "ofellman2@ibm.com",
"groups": [
{
"id": 36
},
{
"id": 7
},
{
"id": 77
},
{
"id": 28
},
{
"id": 32
}
],
"profile": {
"id": 3,
"biography": "In hac habitasse platea dictumst. Morbi vestibulum, velit id pretium iaculis, diam erat fermentum justo, nec condimentum neque sapien placerat ante. Nulla justo.",
"website": "http://cargocollective.com/consequat/in/consequat/ut/nulla/sed.png?dictumst=faucibus",
"profilePictureUrl": "http://dummyimage.com/224x100.png/cc0000/ffffff"
},
"posts": [
{
"id": 4,
"content": "Nam ultrices, libero non mattis pulvinar, nulla pede ullamcorper augue, a suscipit nulla elit ac nulla. Sed vel enim sit amet nunc viverra dapibus. Nulla suscipit ligula in lacus.\n\nCurabitur at ipsum ac tellus semper interdum. Mauris ullamcorper purus sit amet nulla. Quisque arcu libero, rutrum ac, lobortis vel, dapibus at, diam.",
"comments": [
{
"id": 349493,
"test": {
"id": "Operative actuating budgetary management"
},
"author": {
"id": 2
}
},
{
"id": 909598,
"test": {
"id": "Profound next generation product"
},
"author": {
"id": 4
}
},
{
"id": 247072,
"test": {
"id": "Team-oriented multimedia analyzer"
},
"author": {
"id": 9
}
}
]
},
{
"id": 5,
"content": "Integer ac leo. Pellentesque ultrices mattis odio. Donec vitae nisi.",
"comments": [
{
"id": 572489,
"test": {
"id": "Open-source optimizing focus group"
},
"author": {
"id": 5
}
},
{
"id": 133947,
"test": {
"id": "Self-enabling attitude-oriented success"
},
"author": {
"id": 1
}
},
{
"id": 496573,
"test": {
"id": "Profit-focused multimedia firmware"
},
"author": {
"id": 8
}
},
{
"id": 137780,
"test": {
"id": "Self-enabling intermediate info-mediaries"
},
"author": {
"id": 2
}
}
]
},
{
"id": 6,
"content": "Maecenas tristique, est et tempus semper, est quam pharetra magna, ac consequat metus sapien ut nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris viverra diam vitae quam. Suspendisse potenti.\n\nNullam porttitor lacus at turpis. Donec posuere metus vitae ipsum. Aliquam non mauris.",
"comments": []
}
]
},
{
"id": 4,
"username": "cpourvoieur3",
"email": "mlisimore3@unicef.org",
"groups": [
{
"id": 27
},
{
"id": 49
},
{
"id": 21
},
{
"id": 48
},
{
"id": 90
},
{
"id": 57
},
{
"id": 19
},
{
"id": 10
}
],
"profile": {
"id": 4,
"biography": "Curabitur gravida nisi at nibh. In hac habitasse platea dictumst. Aliquam augue quam, sollicitudin vitae, consectetuer eget, rutrum at, lorem.",
"website": "http://slideshare.net/rutrum/nulla/tellus/in/sagittis/dui.png?duis=volutpat&at=erat",
"profilePictureUrl": "http://dummyimage.com/167x100.png/cc0000/ffffff"
},
"posts": [
{
"id": 7,
"content": "Vestibulum ac est lacinia nisi venenatis tristique. Fusce congue, diam id ornare imperdiet, sapien urna pretium nisl, ut volutpat sapien arcu sed augue. Aliquam erat volutpat.\n\nIn congue. Etiam justo. Etiam pretium iaculis justo.\n\nIn hac habitasse platea dictumst. Etiam faucibus cursus urna. Ut tellus.",
"comments": []
},
{
"id": 8,
"content": "Sed ante. Vivamus tortor. Duis mattis egestas metus.\n\nAenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.\n\nQuisque id justo sit amet sapien dignissim vestibulum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est. Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros.",
"comments": []
},
{
"id": 9,
"content": "Duis aliquam convallis nunc. Proin at turpis a pede posuere nonummy. Integer non velit.\n\nDonec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.\n\nDuis bibendum. Morbi non quam nec dui luctus rutrum. Nulla tellus.",
"comments": [
{
"id": 727396,
"test": {
"id": "Business-focused maximized framework"
},
"author": {
"id": 6
}
},
{
"id": 529333,
"test": {
"id": "Fully-configurable client-driven Graphic Interface"
},
"author": {
"id": 1
}
},
{
"id": 698528,
"test": {
"id": "Diverse methodical framework"
},
"author": {
"id": 1
}
},
{
"id": 570927,
"test": {
"id": "Profound interactive infrastructure"
},
"author": {
"id": 5
}
}
]
},
{
"id": 10,
"content": "Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.\n\nAenean lectus. Pellentesque eget nunc. Donec quis orci eget orci vehicula condimentum.",
"comments": [
{
"id": 374234,
"test": {
"id": "Profit-focused fresh-thinking success"
},
"author": {
"id": 6
}
},
{
"id": 266342,
"test": {
"id": "Advanced optimizing Graphic Interface"
},
"author": {
"id": 6
}
},
{
"id": 970877,
"test": {
"id": "Multi-lateral web-enabled synergy"
},
"author": {
"id": 8
}
},
{
"id": 635357,
"test": {
"id": "Centralized real-time time-frame"
},
"author": {
"id": 5
}
},
{
"id": 818761,
"test": {
"id": "Profit-focused secondary middleware"
},
"author": {
"id": 2
}
}
]
}
]
},
{
"id": 5,
"username": "driddoch4",
"email": "hfreake4@simplemachines.org",
"groups": [
{
"id": 68
},
{
"id": 52
},
{
"id": 18
},
{
"id": 82
},
{
"id": 29
},
{
"id": 55
},
{
"id": 95
}
],
"profile": {
"id": 5,
"biography": "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus vestibulum sagittis sapien. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
"website": "http://jiathis.com/turpis/enim/blandit/mi/in.html?duis=augue&mattis=vel&egestas",
"profilePictureUrl": "http://dummyimage.com/181x100.png/5fa2dd/ffffff"
},
"posts": [
{
"id": 11,
"content": "Phasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.\n\nProin eu mi. Nulla ac enim. In tempor, turpis nec euismod scelerisque, quam turpis adipiscing lorem, vitae mattis nibh ligula nec sem.\n\nDuis aliquam convallis nunc. Proin at turpis a pede posuere nonummy. Integer non velit.",
"comments": [
{
"id": 668167,
"test": {
"id": "Decentralized static application"
},
"author": {
"id": 2
}
},
{
"id": 645021,
"test": {
"id": "Progressive cohesive complexity"
},
"author": {
"id": 8
}
},
{
"id": 904836,
"test": {
"id": "Right-sized human-resource middleware"
},
"author": {
"id": 2
}
},
{
"id": 193482,
"test": {
"id": "Organized bottom-line monitoring"
},
"author": {
"id": 3
}
}
]
},
{
"id": 12,
"content": "Donec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.",
"comments": [
{
"id": 820497,
"test": {
"id": "Open-architected well-modulated structure"
},
"author": {
"id": 10
}
}
]
},
{
"id": 13,
"content": "Nulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.\n\nCras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.\n\nQuisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus.",
"comments": [
{
"id": 214834,
"test": {
"id": "Face to face content-based contingency"
},
"author": {
"id": 4
}
},
{
"id": 361052,
"test": {
"id": "Vision-oriented client-driven emulation"
},
"author": {
"id": 10
}
},
{
"id": 819369,
"test": {
"id": "Decentralized clear-thinking projection"
},
"author": {
"id": 9
}
}
]
},
{
"id": 14,
"content": "Integer ac leo. Pellentesque ultrices mattis odio. Donec vitae nisi.\n\nNam ultrices, libero non mattis pulvinar, nulla pede ullamcorper augue, a suscipit nulla elit ac nulla. Sed vel enim sit amet nunc viverra dapibus. Nulla suscipit ligula in lacus.",
"comments": []
}
]
},
{
"id": 6,
"username": "dvelte5",
"email": "shuke5@canalblog.com",
"groups": [
{
"id": 86
},
{
"id": 28
},
{
"id": 8
},
{
"id": 22
}
],
"profile": {
"id": 6,
"biography": "Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui.",
"website": "http://amazon.co.jp/eget/semper.aspx?faucibus=habitasse&cursus=platea&urna",
"profilePictureUrl": "http://dummyimage.com/179x100.png/dddddd/000000"
},
"posts": []
},
{
"id": 7,
"username": "mhallor6",
"email": "gpetters6@tiny.cc",
"groups": [
{
"id": 49
},
{
"id": 56
},
{
"id": 24
}
],
"profile": {
"id": 7,
"biography": "Nam ultrices, libero non mattis pulvinar, nulla pede ullamcorper augue, a suscipit nulla elit ac nulla. Sed vel enim sit amet nunc viverra dapibus. Nulla suscipit ligula in lacus.",
"website": "https://globo.com/eget/nunc.js?justo=nulla&eu=neque&massa=libero&donec=convallis&dapibus",
"profilePictureUrl": "http://dummyimage.com/122x100.png/ff4444/ffffff"
},
"posts": [
{
"id": 15,
"content": "Nulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.",
"comments": [
{
"id": 380358,
"test": {
"id": "Optimized user-facing software"
},
"author": {
"id": 1
}
}
]
}
]
},
{
"id": 8,
"username": "nbergeau7",
"email": "mkibbel7@tmall.com",
"groups": [
{
"id": 24
},
{
"id": 54
},
{
"id": 95
}
],
"profile": {
"id": 8,
"biography": "Suspendisse potenti. In eleifend quam a odio. In hac habitasse platea dictumst.",
"website": "http://people.com.cn/odio/consequat/varius.jsp?eros=nullam&vestibulum=varius&ac=nulla",
"profilePictureUrl": "http://dummyimage.com/230x100.png/5fa2dd/ffffff"
},
"posts": [
{
"id": 16,
"content": "Pellentesque at nulla. Suspendisse potenti. Cras in purus eu magna vulputate luctus.\n\nCum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus vestibulum sagittis sapien. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.\n\nEtiam vel augue. Vestibulum rutrum rutrum neque. Aenean auctor gravida sem.",
"comments": []
},
{
"id": 17,
"content": "Sed ante. Vivamus tortor. Duis mattis egestas metus.\n\nAenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.",
"comments": [
{
"id": 886087,
"test": {
"id": "Total demand-driven infrastructure"
},
"author": {
"id": 9
}
},
{
"id": 235353,
"test": {
"id": "Versatile mobile monitoring"
},
"author": {
"id": 4
}
},
{
"id": 264476,
"test": {
"id": "Future-proofed static initiative"
},
"author": {
"id": 2
}
}
]
},
{
"id": 18,
"content": "Praesent id massa id nisl venenatis lacinia. Aenean sit amet justo. Morbi ut odio.",
"comments": [
{
"id": 299869,
"test": {
"id": "Pre-emptive 3rd generation pricing structure"
},
"author": {
"id": 5
}
},
{
"id": 82649,
"test": {
"id": "Public-key holistic pricing structure"
},
"author": {
"id": 2
}
},
{
"id": 675757,
"test": {
"id": "Function-based homogeneous monitoring"
},
"author": {
"id": 4
}
},
{
"id": 321146,
"test": {
"id": "Proactive intangible ability"
},
"author": {
"id": 1
}
},
{
"id": 754321,
"test": {
"id": "Operative zero tolerance complexity"
},
"author": {
"id": 3
}
}
]
}
]
},
{
"id": 9,
"username": "ddebruin8",
"email": "zdavidovits8@aol.com",
"groups": [
{
"id": 40
},
{
"id": 67
},
{
"id": 67
},
{
"id": 13
},
{
"id": 50
},
{
"id": 34
},
{
"id": 53
}
],
"profile": {
"id": 9,
"biography": "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus vestibulum sagittis sapien. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
"website": "http://shinystat.com/nec/sem/duis/aliquam/convallis/nunc.json?eget=facilisi&semper=cras",
"profilePictureUrl": "http://dummyimage.com/229x100.png/5fa2dd/ffffff"
},
"posts": [
{
"id": 19,
"content": "Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.\n\nAenean lectus. Pellentesque eget nunc. Donec quis orci eget orci vehicula condimentum.",
"comments": [
{
"id": 246330,
"test": {
"id": "Cross-platform needs-based firmware"
},
"author": {
"id": 10
}
},
{
"id": 340531,
"test": {
"id": "Total content-based data-warehouse"
},
"author": {
"id": 9
}
},
{
"id": 145457,
"test": {
"id": "Programmable transitional budgetary management"
},
"author": {
"id": 3
}
},
{
"id": 13492,
"test": {
"id": "Switchable encompassing archive"
},
"author": {
"id": 8
}
},
{
"id": 310239,
"test": {
"id": "Self-enabling attitude-oriented strategy"
},
"author": {
"id": 4
}
}
]
},
{
"id": 20,
"content": "Morbi porttitor lorem id ligula. Suspendisse ornare consequat lectus. In est risus, auctor sed, tristique in, tempus sit amet, sem.\n\nFusce consequat. Nulla nisl. Nunc nisl.",
"comments": []
},
{
"id": 21,
"content": "Donec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.\n\nDuis bibendum. Morbi non quam nec dui luctus rutrum. Nulla tellus.\n\nIn sagittis dui vel nisl. Duis ac nibh. Fusce lacus purus, aliquet at, feugiat non, pretium quis, lectus.",
"comments": [
{
"id": 909627,
"test": {
"id": "Expanded intangible forecast"
},
"author": {
"id": 7
}
}
]
}
]
},
{
"id": 10,
"username": "lwoodrooffe9",
"email": "mhowatt9@kickstarter.com",
"groups": [
{
"id": 31
},
{
"id": 44
},
{
"id": 38
},
{
"id": 14
}
],
"profile": {
"id": 10,
"biography": "Nulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.",
"website": "https://blinklist.com/semper/est/quam/pharetra/magna/ac.png?sagittis=eu&nam=tincidunt",
"profilePictureUrl": "http://dummyimage.com/121x100.png/5fa2dd/ffffff"
},
"posts": [
{
"id": 22,
"content": "Sed ante. Vivamus tortor. Duis mattis egestas metus.\n\nAenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.\n\nQuisque id justo sit amet sapien dignissim vestibulum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est. Donec odio justo, sollicitudin ut, suscipit a, feugiat et, eros.",
"comments": [
{
"id": 831777,
"test": {
"id": "Open-source non-volatile help-desk"
},
"author": {
"id": 7
}
},
{
"id": 883941,
"test": {
"id": "Fully-configurable mission-critical emulation"
},
"author": {
"id": 6
}
},
{
"id": 759548,
"test": {
"id": "Right-sized bifurcated strategy"
},
"author": {
"id": 10
}
},
{
"id": 294894,
"test": {
"id": "Balanced explicit paradigm"
},
"author": {
"id": 3
}
}
]
}
]
}
]