Spring Boot and JPA Practical Mastery Roadmap

· Tech· Spring
Spring

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!####

  1. Embedded types can be used interchangeably with collection value types.
  2. 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
  1. 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_lib.png 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_layer.png

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 테크

    15 / 164