Spring Boot and JPA Practical Mastery Roadmap
This is a summary of Spring boot + JPA + Querydsl learning contents based on Kim Young-han's Roadmap for Complete Mastery of Spring Boot and JPA Practices.
Relationship mapping
Multiplicity, one-way VS two-way, master of correlation I think this is the part that people learning JPA for the first time may find most confusing and difficult. The following contents are a summary of correlation mapping based on the learning contents of Younghan Kim's Infrun Roadmap. An association relationship refers to a method of referencing objects in object-oriented design, just like setting up an RDB relationship using a foreign key in a database. Related relationships, for example, if there are multiple students in a class, the class and students are
1:N 매핑
It can be explained as follows. In this way, JPA provides a way to establish relationships using object references without foreign keys.
Considerations when mapping relationships
Association relationships are largely divided into one-way and two-way mapping. A one-way relationship means that one side refers to the other relationship, while a two-way relationship allows references from both sides. In an association relationship, the owner is determined and a reference is made for simple reading in the non-owner direction, but in practice, two-way mapping is often used to easily retrieve data from both sides. It is recommended that the owner of the association is set to an entity that is mapped to a table (N) with a foreign key. On the other side
mappedBy
You must map a variable name that allows you to know the relationship. The reason why the location where the foreign key is located is set as the owner is that when working with a database, JPA performs work centered on the entity that is the owner. This is because foreign key values are updated together, making management and maintenance easier. When using associations, it is best to always set values on both sides to account for pure object state. Therefore, instead of a setter method for one entity, it is recommended to specify a separate, easy-to-understand method so that it can automatically be set even when the relationship is reversed. This makes it convenient to set both values without forgetting them.
Relationship types and strategies
Many-to-one [N:1]
By mapping
다대일 양방향
It is recommended to use . I wrote the relationship entity for the student and class that I used as the first example in code. In the code below
다대일 양방향 매핑
It is in this state.
// 학생 entity
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
About @JoinColumn Used when mapping foreign keys.
@JoinColumn
If you don't use
@JoinTable
Because it operates as a strategy, one more unnecessary table is created and it is inefficient to manage.
One-to-many [1:N]
Unlike many-to-one
mappedBy
It has properties. The object being connected here is the partner tied to one-to-many, that is, here
@ManyToOne
Just write the variable name of the entity attribute you set.
// 팀 entity
@OneToMany(mappedBy="student")
@JoinColumn(name="STUDENT_ID")
private List<Student> students = new ArrayList<>();
One-to-one [1:1]
일대일 양방향
silver
다대일 양방향
Similar to mapping. However, since it is one-to-one, you can put the foreign key in any direction, but you need to think about how to set the owner in the future by adding features and collaborating with the DBA. In general, inserting a foreign key into the target table, as in traditional database design, has the advantage of maintaining the table structure even when changing to one-to-many mapping later. However, due to the limitations of the proxy function, it has the disadvantage that it is always loaded immediately even if it is set to delayed loading.
// 팀 entity
@OneToOne(mappedBy="student")
@JoinColumn(name="STUDENT_ID")
private Student student;
// 학생 entity
@OneToOne
@JoinColumn(name="TEAM_ID")
private Team team;
Many-to-many [N:M]
It is not used in practice in a way that cannot be expressed in relational databases. You must create an intermediate table and create a one-to-many or many-to-one relationship. However, rather than managing the intermediate table separately, it is recommended to promote the intermediate table to an entity and manage it.
@ManyToMany(fetch=LAZY)
@JoinTable(name="CATEGORY_ITEM",
joinColumn=@JoinColumn(name="CATEGORY_ID"),
inverseJoinColumns=@JoinColumn(name="ITEM_ID")
)
private List<Item> items = new ArrayList<>();
Immediate loading and delayed loading
Fetch strategy in correlation properties
fetchType.LAZY
Select lazy loading, or
fetchType.EAGER
You can set instant loading. However, it is recommended to avoid using immediate loading because performance may deteriorate or unexpected queries may occur.
@ManyToOne
,
@OneToOne
Because the default is immediate loading,
@ManyToOne(fetch=fetchType.LAZY)
You must set up lazy loading to prevent risks. instead
JPQL fetch 조인
Ina
엔티티 그래프 기능
You can solve this problem by using .
Permanence Transition CASCADE
It is used when you want to make a specific entity persistent and also make related entities persistent. It has nothing to do with affinity mapping, but
cascade=CascadeType.PERSIST
Because it is written with an annotation strategy, it is classified here.
CASECADE
In the type
ALL
,
PERSIST
,
REMOVE
,
MERGE
,
REFRESH
,
DETACH
There is. It is recommended that persistence transitives only apply to child entities that are not associated with another parent.
Orphan object
An orphan object is a child entity that has lost its relationship with its parent entity.
orphanRemoval = true
It is used together with . After setting, if one is deleted from the parent entity's child list, no relationship is established with that child entity. It is recommended to only use it when the specific entity is privately owned.
CascadeType.REMOVE
If the parent entity is deleted, the child entities are also removed.
CASCADE
By using both and orphan objects, you can manage the entire life cycle of child entities. (Useful when implementing DDD's Aggregate Root concept)
Inheritance relationship mapping
@Inheritance
When creating an entity with an inheritance relationship, JPA
extends
It is based on a strategy of putting all the entities in one single table. However, if you want to handle inheritance mapping in a different way,
@Inheritance
You can use another strategy.
@Inheritance(strategy=InherianceType.JOINED) // 조인전략
@Inheritance(strategy=InherianceType.SINGLE_TABLE) // 단일 테이블 전략
@Inheritance(strategy=InherianceType.TABLE_PER_CLASS) // 구현 클래스마다 테이블 전략
The single table strategy has the disadvantage of having all the columns of the inherited table, so there are many areas where nulls must be allowed. In cases where important design is required considering relationships or expansion between tables,
JOIN
It's a good idea to use a strategy.
TABLE_PER_CLASS
Strategies are not recommended because they are not good at figuring out inheritance mappings.
@DiscriminatorColumn
It is used for distinction in the parent class. In single table strategy, automatically
@DiscriminatorColumn
As a column that can be distinguished without using
DTYPE
This is created. But
JOIN
In strategy
DTYPE
This is because constraints are created in the child table without being created, so a separate separator column is not needed.
@DiscriminatorValue
Parent class from child class
@DiscriminatorColumn
It is used to distinguish the mapping table of inheritance relationships by the value to be entered in the column. in child class
DTYPE
You can specify a value to enter.
@MappedSupperclass
Use when you want to use common mapping attribute information. If you need information on who modified the information and the date and time of modification for all tables,
BaseEntity
to provide only mapping information. This class is not an entity, but is simply for assigning common properties and is not a concept for creating an inheritance table.
Take a look at the code
In the parent class of the inheritance relationship
@Inheritance
Wow
@DiscriminatorColumn
Specify to allow child classes to be inherited. At this time, the child class
@DiscriminatorValue
by
DTYPE
Set the value to enter,
extends
Indicates inheritance.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter
@Setter
@Table(name = "ITEM")
public class Item {
@Id @GeneratedValue
@Column(name="item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<>();
}
@Entity
@DiscriminatorValue(value = "M")
@Getter
@Setter
public class Movie extends Item{
private String director;
private String actor;
}
Relationship with proxy
It is a function that is created by inheriting the actual class and is searched using a proxy.
em.getReference()
When used, the proxy object is looked up and, if there is already an entity in the persistence context, the actual entity is returned. For reference,
em.find()
queries the actual object.
Proxy objects and persistence context
Here, proxy object is a fake object that allows JPA to delay the actual database lookup. The proxy object has the actual object as a target variable, and when queried, an initialization request is sent to the persistence context using the reference value to query the actual object. Since the target reference is taken after it has been searched once, initialization is not requested from the second time. In summary, when initializing a proxy object, the proxy object does not change to a real entity, and once initialized, the real entity can be accessed through the proxy object.
Take a look at the code
Here, m1 and m2 mean that they are all retrieved from the same proxy object. Therefore, even if we do an equal comparison (==) here,
true
You can see that comes out. When comparing proxy object types:
instanceOf
You must use .
Member m1 = em.getReference(Member.class, "m1Id");
Member m2 = em.getReference(Member.class, "m1Id");
System.out.println(m1 == m2); // true
First create a proxy object,
getName()
The moment you make a query, the actual object is referenced through an initialization request to the persistence context. After declaring the same object
m2
In this case, the actual object is viewed directly. This is because it is already an object managed by the persistence context.
Member m1 = em.getReference(Member.class, "m1Id"); // 프록시 객체
m1.getName(); // 초기화 요청
Member m2 = em.getReference(Member.class, "m1Id"); // 실제 객체
Java ORM Standard JPA Programming - Basic Proxy Objects and Eager & Lazy Fetch Types in Hibernate [JPA] Persistence transitivity, orphan objects (cascade scope) ##OSIV Since Spring Boot uses Open Session In View as the default, the persistence context is kept open until the view rendering ends. In semi-persistent state, if you initialize the proxy, hibernate will
org.hibernate.LazyInitializationException
Exceptions may occur. This problem is in spring
@Transaction
This occurs in the process of using , and is a common problem that can be seen in the process of DTO processing of an entity received from the service side from the controller to the mapper.
@Transaction
When this specified service-side method is terminated, a problem occurs in which the mapper does not properly process the actual object because the object is not managed in the persistence context.
[Practical action! Spring Boot and JPA Utilization 2 - API development and performance Optimization](https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED% 8A%B8-JPA-API%EA%B0%9C%EB%B0%9C-%EC%84%B1%EB%8A%A5%EC%B5%9C%EC%A0%81%ED%99%94)
data type
JPA type is entity type(
@Entity
It can be classified into value type (recognized as an identifier) and (untraceable type with only value without identifier). Value types should never be shared because they are untraceable. In other words, only sharable address values such as Interger and String are passed, so they cannot be changed. Types of value types in JPA include default value type, embedded type, and collection value type. If the value type is a primitive type, compare for equality.
==
You can use , but when comparing values such as embedded types, use
equals()
You must use to compare equivalence. Let’s learn more about immutable objects! In Java, Integer or String are immutable objects. These objects cannot be changed because only shareable address values are passed. For example, Integer a = 5; and Integer b = a; If there is a code, a and b will share the same address value. Therefore, if you change either a or b, the other does not change together, but a new object is created. A value type that can be used as part of an entity in JPA.
Embedded types (complex value types)
Embedded types are also not entity types, so they are not traceable. You can use the Address object as a value type in the Member entity.
@Embedded
Specify that it is an embedded type value,
@Embeddable
specifies that the class is an embedded type. In the field, values frequently used in entities, such as addresses or contact information, are defined and used as embedded types. For example, in the Member entity, the member's name, age, address, and contact information are often used. These values can be defined as embedded types and used in the Member entity. Storing value types separately in separate tables may result in poor performance because joins occur. Even in these cases, there is a performance advantage in using embedded types because they are stored together with the Member table. Additionally, using embedded types improves code readability and makes object handling code more concise. Therefore, embedded types are often used in the field because they enable object-oriented design. In the code below, the Address object is created and stored as an individual column along with the Member table, rather than being created as a separate table.
@Entity
public class Member {
@Id
private Long id;
private String name;
@Embedded
private Address address;
// ...
}
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
// ...
}
In embedded type
@Embeddable
When an object is shared, a problem may arise where changes occur in all entities that use the object. In this case, because tracking is difficult, you must create an immutable object for the shared reference.
setter
Methods should not be used, and if changes are needed, it is safe to modify the values by recreating a new object. [Problems when not set as an immutable object]
Address address = new Address("city", "street", "10000");
Member member1 = new Member();
member1.setName("member1");
member1.setHomeAddress(address);
Member member2 = new Member();
member2.setName("member2");
member2.setHomeAddress(address);
em.persist(member1);
em.persist(member2);
address.setCity("seoul");
// member1과 member2 모두 address의 city가 'seoul'로 변경됩니다.
[Change the value of the embedded type when set as an immutable object]
Address address = new Address("city", "street", "10000");
Member member1 = new Member();
member1.setName("member1");
member1.setHomeAddress(address);
em.persist(member1);
// 수정이 필요한 경우
Address newAddress = new Address("seoul", address.getStreet(), address.getZipcode());
member1.setHomeAddress(newAddress);
Wouldn't it be possible to use BaseEntity? This is where a question arose. If it is a common attribute
BaseEntity
There is also a way to manage it, but I thought about the big difference in setting it as an embedded value type for a column.
@Embedded
Wow
@Embeddable
allows you to group multiple values in one entity. These grouped values can also be reused by other entities. Additionally, since value types can be managed separately in separate tables, data consistency and redundancy can be reduced. Even in the conceptual aspect
BaseEntity
defines common columns with the concept of inheritance. But
@Embedded
The method used is in the column called address.
우편번호, 기본주소, 상세주소
The difference is that it specifies a single grouped property such as . In conclusion, It is desirable to use the inheritance concept to manage common properties of multiple entities in a group, and it is appropriate to use embedded value types to group detailed properties of one property.
Collection value type
It is used to store one or more value types and uses Java's collection. Value type collection is
CASCADE
It has the default value, and the cascade + orphan object removal function before persistence can be seen as essential. If changes occur in the value type collection, all data associated with the host entity is deleted and all current values in the value type collection are stored again.
@ElementCollection
You can use a collection type, and a table with the parent ID as the primary key is automatically created.
member_roles
A table called is automatically created.
@CollectionTable
You can explicitly specify table properties to be used for collection value types.
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
@Setter
private Long id;
@Column
@ElementCollection
private List<String> roles = new ArrayList<>();
@ElementCollection
@CollectionTable(name="OPTIONS", joinColumns=@JoinColumn(name="MEMBER_ID"))
private List<String> options = new ArrayList<>();
}
Because the collection value type uses a lazy loading strategy, a query is created at the moment it is needed, such as to look up the collection value type.
Membmer member = em.find(Member.class, member.getId()); // member만 조회
member.getRols(); // 컬렉션 값 타입 조회
Let’s learn this about JPA types!####
- Embedded types can be used interchangeably with collection value types.
- In the field, when the collection value type is not a very simple area,
일대다
or
다대일
It is easy to use entity tracking by changing it to an association relationship. 3. When using untraceable embedded or collection value types, be sure to
불변객체
It must be created and used.
Java ORM Standard JPA Programming - Basic [JPA] Value types and immutable objects - Value types (2) [JPA] Value type collection: @ElementCollection, @CollectionTable [Spring JPA] @Embedded, @Embeddable
Object-oriented query language
Supports various query methods such as using JPQL, JPA Criteria, Query DSL, native SQL, and JDBC API.
When persistence context synchronization is required
If you use the JDBC API or MyBatis with JPA, you must explicitly set the persistence context.
flush()
You have to do it. Because data that has not yet been reflected in the DB is looked up by bypassing JPA, integrity issues arise. Therefore, persistence context synchronization processing is required before bypass access. ###JPQL
Java Persistence Query Language Write queries targeting entity objects using the query language used in JPA. It is similar to SQL, but has the feature of querying entity objects. JPQL abstracts SQL and does not depend on specific database SQL.
TypedQuery
is used when the return type is clear, and if it is not clear, just
Query
Write it as .
// 파라미터 이름을 이용한 바인딩
TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u WHERE u.age > :age ORDER BY u.name DESC", User.class);
query.setParameter("age", 20);
List<User> users = query.getResultList();
// 위치 기반 파라미터를 사용한 바인딩
Query<Member> query = entityManager.createQuery("SELECT m FROM Member m WHERE age=?1");
query.setParameter(1, 20);
Projection
Projection refers to what data to retrieve in the SELECT clause in JPQL. Depending on the data properties being searched, it is divided into object projection, embedded projection, and scalar projection.
SELECT m.name FROM Member m
Paging
When creating a query, the beginning of the result value
setFirstResult
and last
setMaxResults
You can set and process paging to bring the paginated value to the List.
List<Member> query = entityManager.createQuery("SELECT m FROM Member m WHERE age=:age")
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
query.setParameter("age", 20);
Join
Joins include inner join, outer join, and theta join.
세타조인
is used when you want to join two unrelated tables. At this time, the join value is
FULL JOIN
It is joined like so and the value corresponding to the conditional clause is derived from it.
Subquery
EXISTS
,
ALL
,
ANY
,
IN
It provides the same functions as: Hibernate is
SELECT
It also supports the use of subqueries. JPA standard specifications are
WHERE
and
HAVING
It can only be used in temples. But
FROM
There is no subquery in the clause.
ENUM
type including the package name
jpql.MemberType.ADMIN
You can add conditional clauses by substitution or parameter binding.
Conditional statement
COALESCE
allows you to return something other than NULL.
SELECT COALESCE(m.name, '이름이 없다') FROM Member m
It is used together with . If there is no name, ‘No name’ is displayed.
NULLIF
returns NULL if this is the desired value.
SELECT NULLIF(m.name, '홍길동') FROM Member m
Here, if the name is Hong Gil-dong, NULL is returned.
User-defined functions
Before use, you must inherit and register in the DB you are using to use it. Let's learn how to use MySQL's user-defined functions with JPQL. First, to register a user-defined function in MySQL, create a function as follows.
CREATE FUNCTION '함수명' (
파라미터
) RETURNS 반환할 데이터타입
BEGIN
수행할 쿼리
RETURN 반환할 값
END
User-defined functions to use
com.example.MySqlFunctions
You can create a query by using a package containing a user-defined function and entering the registered function name and parameters.
TypedQuery<Employee> query = entityManager.createQuery(
"SELECT e FROM Employee e WHERE FUNCTION('com.example.MySqlFunctions.my_custom_function', e.salary) > 50000",
Employee.class
);
List<Employee> employees = query.getResultList();
Path expression
A path expression is an expression for searching an object graph.
상태필드
refers to a field for storing values.
연관필드
This can cause problems in practice because it causes implicit inner joins with single-valued associated fields. It is important to make the query manageable with explicit joins whenever possible.
SELECT o.customer.name, oi.quantity FROM Order o JOIN o.orderItems oi WHERE o.id = :orderId
fetch join
This is a function provided by JPQL to optimize performance. Like JPA immediate loading, it is the concept of querying the object graph in SQL at once. For example, when searching for members, if you want to search for related teams as well, you can write a query like this:
select m from Member m join fetch m.team
- Subject of patch join
team
It retrieves all of the values, but the disadvantage is that you cannot retrieve only the desired value using an alias. 2. Two or more collections
Cartesian Product
Because it creates a lot of duplicate elements, there may be a lot of overlap. In order to solve this
LEFT JOIN FETCH
You can remove duplicate data by patch joining. 3. Patch join the collection
페이징 API
The downside is that you cannot use . Because all the data is imported, it can be difficult to process this data page by page in the API.
Polymorphic queries
You can also retrieve data from child classes for variables declared as parent class types. Inheritance relationship in advance
@DiscriminatorColumn
Because it is designated as
TYPE
You can access a specific child type using .
SELECT a FROM Animal a WHERE TYPE(a) IN (Dog, Cat)
TREAT
You can use it to treat a parent type as a specific child type. Looking at the code below:
Animal
using an alias
Dog
You can find objects whose age is older than 2 years.
SELECT a FROM Animal a WHERE TREAT(a as Dog).age > 2
Named query
This is a query that can be defined in advance as a static query and used with a name. It has the advantage of being able to be reused after initialization at the time of application loading and verifying queries at the time of loading. It can be defined in the class itself or in XML.
@Entity
@NamedQuery(name = "Person.findByAge", query = "SELECT p FROM Person p WHERE p.age = :age")
public class Person {
@Id
private Long id;
private String name;
private int age;
// getters, setters, constructors
}
TypedQuery<Person> query = em.createNamedQuery("Person.findByAge", Person.class);
query.setParameter("age", 30);
List<Person> persons = query.getResultList();
Bulk operations
When performing a bulk operation, the persistence context is ignored and the database is directly queried, so it is important to initialize the persistence context after the bulk operation.
Query query = em.createQuery("UPDATE Person p SET p.age = :newAge WHERE p.age < :oldAge");
query.setParameter("newAge", 40);
query.setParameter("oldAge", 30);
int updatedCount = query.executeUpdate();
###Querydsl Querydsl started out as a solution to HQL's domain type and string type safety issues, and has now developed into a technology for backend support such as JPA, JDO, JDBC, and MongoDB. Type stability and consistency are important principles of Querydsl. QueryDSL can check for errors at compile time because it builds queries in Java code, like this: Therefore, runtime errors are less likely to occur. It has the advantage of being more readable than JPQL and automatically generating entity aliases and properties. It is also suitable for dynamic query use.
Preparing Querydsl
Gradle settings are required before starting. Based on the fact that you were already working on a spring web project using jpa, the information that needs to be added is as follows. This code contains plugins for using Querydsl, additional dependency libraries, and settings for building.
// build.gradle
plugins {
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
id 'java'
}
dependencies {
implementation 'com.querydsl:querydsl-jpa'
}
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
Here, in order to create a Q type for verification,
Gradle-Task-other-compileQuerydsl
After running
build-generated-querydsl
You can check what was created inside. The Q type for verification is the original type.
public
It is a query type containing properties and is used as follows. To view queries used in Querydsl,
application.yml
You can check it in settings.
spring.jpa.properties.hibernate.use_sql_comments: true
How to use Querydsl
There are two ways to use Q-type objects: using aliases or using default instances. However, you can also use the default instance automatically provided by Querydsl, as in the example above.
QCustomer customer = new QCustomer("myCustomer");
Here, the Q type object
QCustomer
By default
customer
It is provided as , and can be used in the same way within Querydsl.
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
List<Customer> customers = queryFactory.selectFrom(customer)
.where(customer.age.gt(20))
.orderBy(customer.name.desc())
.fetch();
Querydsl core syntax Querydsl, like JPQL, provides most of the SQL functions, and there are additional functions and grammars for convenience. Excluding the basic grammar, I have organized only a few grammars to help you remember them.
Search condition query####
customer.age.goe(30) // age >= 30
customer.age.gt(30) // age > 30
customer.age.loe(30) // age <= 30
customer.age.lt(30) // age < 30
customer.username.like("jane%") // like 검색
customer.username.contains("e") // like %member% 검색
customer.username.startswith("j") // like member% 검색
Join Query
In the join query
innerJoin()
,
leftJoin()
etc. are all supported. From Querydsl 5.0
fetchResult()
Wow
fetchCount()
has been deprecated, but since both queries do not work properly in complex multi-queries, simply
fetch()
After processing java
size()
Processing or paging is recommended. For immediate loading processing after various joins
fetchJoin()
It can be used by adding .
// 연관관계 객체까지 모두 조인, 즉시로딩
selctFrom(customer).leftJoin(customer.order, order).fetchJoin().fetch()
// 즉시로딩 결과 값을 한 개 리턴
selctFrom(customer).leftJoin(customer.order, order).fetchJoin().fetchOne()
Theta join is a method of taking all data and joining it even if there is no correlation.
카디널리티 곱
returns . In case there is no relationship
on절
You can use outer join.
List<Tuple> result = queryFactory
.select(member,team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
Is 'fetchJoin().fetch()' free from N+1 issue? While studying JPA, the most important part of optimizing query performance was
N+1
I think it was part of whether an issue occurred. Querydsl too
fetch
I was wondering if there would be any problem providing joins.
fetchJoin()
Brings an object that has been loaded immediately. here
fetch()
is
fetchJoin()
Because entities that have already been loaded are searched at once without additional queries, duplicate entities are not retrieved.
N+1
issues can be resolved. Additionally, to solve the N+1 issue in JPA,
distinct
The difference is that it removes duplicates of the objects being searched and derives the results. So how should I use Querydsl when using left outer join? Basically, it looks up the Parent object and the Child object
left join()
If you do,
left outer join
Only the necessary children are selected based on the Parent object.
Projection
or solve it using
Result Aggregation
You can group Querydsl results based on specific keys.
// Result Aggression을 이용한 경우
public List<Family> findFamily() {
Map<Parent, List<Child>> transform = queryFactory
.from(parent)
.leftJoin(parent.children, child)
.transform(groupBy(parent).as(list(child)));
return transform.entrySet().stream()
.map(entry -> new Family(entry.getKey().getName(), entry.getValue()))
.collect(Collectors.toList());
}
Map<Integer, List<Comment>> results = query.from(post, comment)
.where(comment.post.id.eq(post.id))
.transform(groupBy(post.id).as(list(comment)));
Projection
Enables more complex control than traditional JPA projection. When there is more than one projection target
Tuple
Ina
DTO
Returns results as a query. Querydsl provides access methods using property setters, direct field access, and constructors. [Method of using property setter] In this case, because dto requires a constructor for each property, you can create a separate constructor or
@NoArgsContructor
You need to create a constructor.
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
[Field Direct Access] No separate constructor is required. If the alias is different
.as()
Solved as and the subquery is
ExpressionUtils.as(sourse, alias)
Solve it this way.
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
[Constructor approach] The order of values and constructors must match,
@AllArgsConstructor
and use
setter
is not required.
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
This constructor method is
@QueryProjection
Supports.
@QueryProjection
By using MemberDto, a concise code expression is possible as shown below. However, it has the disadvantage that it may not be suitable for maintenance because dto is dependent on Querydsl.
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
Dynamic query using BooleanExpression
BooleanExpression
By using , you can create a complex condition.
select
,
where
It can be used as a conditional expression in a clause.
BooleanBuilder
has the advantage of changing the state, null conditions are ignored, and can be reused in other queries.
private List<Member> searchMember(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
.where(usernameEq(usernameCond), ageEq(ageCond))
.fetch();
}
private BooleanExpression usernameEq(String usernameCond) {
return usernameCond != null ? member.username.eq(usernameCond) : null;
}
private BooleanExpression ageEq(Integer ageCond) {
return ageCond != null ? member.age.eq(ageCond) : null;
}
Edit, Delete Bulk operation
Bulk operations such as modification and deletion ignore the persistence context entity and go directly to the DB.
execute()
Because of the processing, it is a good idea to initialize the persistence context.
long count = queryFactory
.delete(member)
.where(member.age.gt(18))
.execute();
em.flush();
em.clear();
How to use Elegant Brothers’ Querydsl
Do not use extends / implements
For every repository
JpaRepository
without inheriting
JPAQueryFactory
to
Bean
Register and use it.
@Configuration
public class QuerydslConfiguration {
@Autowired
EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
@Repository
@RequiredArgsConstructor
public class MemberRepositoryCustom {
private final JpaQueryFactory queryFactory; // 물론 이를 위해서는 빈으로 등록을 해줘야 한다.
}
Use BooleanExpression for dynamic queries
BooleanExpression
Using , you can create complex dynamic queries by ignoring null values and combining them into an interface that represents a single condition.
BooleanBuilder
is
BooleanExpression
It allows you to collect them and use them. It may vary depending on the situation, but usually
BooleanBuilder
When using , it is difficult to understand at a glance because it is assembled from multiple expressions. And to recycle redundant conditional expressions,
BooleanExpression
It is advantageous to methodize it as .
Do not use exist method
count
Since it operates as a query, performance may deteriorate if the entire query is searched. And because it automatically creates a subquery at every execution, it is difficult for developers to optimize it themselves. instead
join
Using can optimize performance.
Avoid Cross Joins
Implicit joins that do not specify joins may result in poor performance because they result in cross joins. Therefore, unnecessary lookups should be reduced through explicit joins.
When searching, retrieve DTOs rather than Entities first, and refrain from using Entities in the Select column.
Fetching the Entity itself will use the primary cache functionality of the persistence context and query unnecessary columns. and
OneToOne
N+1
There is a problem occurring with the query. Therefore, it is desirable to receive and return only the necessary properties as a DTO. Especially
distinct
When using , it is desirable to search only the absolutely necessary columns because all rows are checked.
Group By Optimization
Unlike MySQL, Querydsl
OrderByNull
is not provided. And the sorting algorithm that runs automatically if there is no index.
Filesort
This will work.
public class OrderByNull extends OrderSpecifier {
public static final OrderByNull DEFAULT = new OrderByNull();
private OrderByNull(){
super(Order.ASC, NullExpression.DEFAULT, NullHandling.Default);
}
}
Using covering index in Querydsl
You gain performance benefits by processing quickly through index searches and accessing data blocks only for filtered items. However, there is a disadvantage that the number of indexes can increase.
Using No Offset to improve paging performance
When there is more data
offset
limit
Because performance is not good when querying data using
No Offset
It is recommended to use to find and read the starting point by index.
Optimizing batch updates
When doing bulk updates
Cache Eviction
You need to process it. Using dirty checking in a persistence context can result in many queries and can be a performance disadvantage, so it is recommended to do batch updates.
Avoid Bulk Insert with JPA
In JPA, insert merge is not applied when auto_increment is set, so if you need this function,
JdbcTemplate
You can use .
Querydsl Doc Querydsl GitHub Practical action! Querydsl Java ORM Standard JPA Programming - Basic Chapter 10 Object-Oriented Query Language JPQL DOC When Left Outer Join is required in OneToMany relationship in Querydsl JPAQuery.fetchResults() is deprecated, how should I replace it? [Uacon 2020] Using QUERYDSL in billions of cases 1. Covering Index (Basic Knowledge / WHERE / GROUP BY)
Hibernate
In addition to Hibernate, JPA implementations include EclipseLink, OpenJPA, and DataNucleus. Among them, we would like to learn about Hibernate, which is the most used. Hibernate is an object-relational matching framework for the Java language. If you use JPA in an actual Spring project, you will see that Hibernate includes libraries.
Hibernate is a JPA implementation that inherits SessionFactory, Session, and Transaction and implements Impl for each. It has the advantage of being able to replace SQL with only the methods provided by Hibernate without using the JDBC API. Therefore, you can focus on business logic and enable object-oriented development.

Hibernate ORM 5.4.33.Final User Guide [JPA] JPA, Hibernate, and Spring Data JPA
Comments
No comments yet. Be the first!
164 posts in 테크
- 2233D Gaussian Splatting vs Unreal Engine: Two Ways to Build a 3D World — and Where Each One Ships
- 222LLMs Inside Unreal Engine: The New Skills Game Developers Need in 2026
- 220Living With Claude Fable 5: How the Most Capable Model Changes the Way You Actually Work
- 219Luma's Bet: From Video Generator to a Single Model That Thinks in Pixels
- 215The Best AI Video Models in 2026: Types, Differences, and Where This Is All Going