๐ก When the key generation strategy is set to
generationType.IDENTITY, the insert query cannot be delayed (์ฐ๊ธฐ์ง์ฐ ์ฌ์ฉ๋ถ๊ฐ). This is becausegenerationType.IDENTITY's functioning mechanism is to generate query to the database as soon as it is called to prevents duplicate key generation from to external transactions. Therefore, you cannot keep it on hold before theem.flush()ortransaction.commit.
@Enumerated@Enumerated(EnumType.*STRING*) ์ผ๋ก ์ฌ์ฉ๊ถ์ฅORDINAL ์ 0,1,2.. ๊ฐ์ผ๋ก ๋ค์ด๊ฐ๊ธฐ ๋๋ฌธ์ ์ถํ ์์๊ฐ ๋ฐ๋ ๊ฐ๋ฅ์ฑ์๋ค.
// Basic
List<User> findByNameAndPassword(String name, String password);
// distinct
List<User> findDistinctUserByNameOrPassword(String name, String password);
List<User> findUserDistinctByNameOrPassword(String name, String password);
// ignoring case
List<User> findByNameIgnoreCase(String name);
List<User> findByNameAndPasswordAllIgnoreCase(String name, String password);
// Order
List<Person> findByNameOrderByNameAsc(String name);
List<Person> findByNameOrderByNameDesc(String name);
// Paging
Page<User> findByName(String name, Pageable pageable); // Page ๋ ์นด์ดํธ์ฟผ๋ฆฌ ์ํ๋จ
Slice<User> findByName(String name, Pageable pageable); // Slice ๋ ์นด์ดํธ์ฟผ๋ฆฌ ์ํ์๋จ
List<User> findByName(String name, Sort sort);
List<User> findByName(String name, Pageable pageable);
// Stream (stream ๋ค์ดํ ์์ ํด์ ํด์ค์ผํ๋ฏ๋ก try with resource ์ฌ์ฉ์ถ์ฒ)
Stream<User> readAllByNameNotNull();
// ์ฒซ ํ์ด์ง (ํ์ด์ง ์ฌ์ด์ฆ = 2)
Pageable firstPageWithTwoElements = PageRequest.of(0, 2);
// ๋๋ฒ์งธ ํ์ด์ง (ํ์ด์ง ์ฌ์ด์ฆ = 5)
Pageable secondPageWithFiveElements = PageRequest.of(1, 5);
// ํ์ด์ง ์ฌ์ฉ
List<Product> allTenDollarProducts =
productRepository.findAllByPrice(10, secondPageWithFiveElements);
Page<Product> allProductsSortedByName = productRepository.findAll(Sort.by("name").accending());
Pageable sortedByName = PageRequest.of(0, 3, Sort.by("name"));
Pageable sortedByPriceDesc = PageRequest.of(0, 3, Sort.by("price").descending());
// ์ด์ค์ ๋ ฌ
Pageable sortedByPriceDescNameAsc = PageRequest.of(0, 5, Sort.by("price").descending().and(Sort.by("name")));
pageable.getTotalPages() : total number of pages
pageable.getTotalElements() : total number of elements
pageable.getNumber() : current page number
pageable.getSize() : number of data per page
pageable.hasnext() : whether there is a next page or not
pageable.isFirst() : if the current page is the first page
pageable.getContent(), PageRequest.get() : returns content on the page
getContext will return List<Entity>, get() will return Stream<Entity>
public interface UserRepository extends JpaRepository<User, Long> {
// Default ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ findById์ Optional์ ๋ด๋ถ์ ์ผ๋ก ์ฒ๋ฆฌ
default User findUserById(Long id) {
return findById(id).orElseThrow(() -> new DataNotFoundException("User not found with id: " + id));
}
}
ifPresentpublic interface UserRepository extends JpaRepository<User, Long> {
// ์ฌ์ฉ์ ID๋ก ์ฌ์ฉ์๋ฅผ ์ฐพ๊ณ , ์กด์ฌํ ๊ฒฝ์ฐ ์ฐ๋ฝ์ฒ ์ ๋ณด๋ฅผ ์
๋ฐ์ดํธํ๋ ๋ฉ์๋
default void updateUserContact(Long userId, String newContact) {
findById(userId).ifPresent(user -> {
user.setContact(newContact);
save(user);
});
}
}
Slice<T>offset field for querying.limit+1 query is performed. (Offset is not efficient, so it is not commonly used in real-world applications.)Sample return of Slice
{
"content": [
{ "id": 13, "username": "User 12", "address": "Korea", "age": 12 },
...
{ "id": 16, "username": "User 15", "address": "Korea", "age": 15 }
],
"pageable": {
"sort": { "sorted": false, "unsorted": true, "empty": true },
"pageNumber": 3,
"pageSize": 4,
"offset": 12,
"paged": true,
"unpaged": false
},
"number": 3,
"numberOfElements": 4,
"first": false,
"last": false,
"size": 4,
"sort": { "sorted": false, "unsorted": true, "empty": true },
"empty": false
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u, u.password AS customField FROM User u WHERE u.username = ?1")
List<User> findByUsernameWithCustomField(String username, Sort sort);
@Query("SELECT u FROM User u WHERE u.username = ?1")
List<User> findByUsername(String username, Sort sort);
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u, u.password AS customField FROM User u WHERE u.username = :username")
List<User> findByUsernameWithCustomField(String username, Sort sort);
@Query("SELECT u FROM User u WHERE u.username = :username")
List<User> findByUsername(String username, Sort sort);
}
Type-Safe Queries:
Fluent API:
QPerson person = QPerson.person;
queryFactory.selectFrom(person)
.where(person.name.eq("John"))
.fetch();Supports Multiple Backends:
Dynamic Queries:
if (name != null) {
predicate = predicate.and(person.name.eq(name));
}
if (age != null) {
predicate = predicate.and(person.age.eq(age));
}Auto-generated Q Classes:
Q classes (e.g., QPerson for a Person entity).Imagine you have a Person entity:
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int age;
}
QueryDSL will generate a QPerson class that mirrors this entity:
public class QPerson extends EntityPathBase<Person> {
public static final QPerson person = new QPerson("person");
public final StringPath name = createString("name");
public final NumberPath<Integer> age = createNumber("age", Integer.class);
...
}
This allows you to write type-safe queries using the QPerson class like this:
QPerson person = QPerson.person;
List<Person> result = queryFactory.selectFrom(person)
.where(person.name.eq("John")
.and(person.age.gt(25)))
.fetch();
person.name.eq("John") ensures the name field is accessed safely and that any issues are caught at compile time.
build.gradle// queryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
Main Application File
Add the following annotations:
@EnableJpaAuditing
@SpringBootApplication
public class Application {
Entity Class
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class TimeStamp {
@CreatedDate
private LocalDateTime createdAt;
@CreatedBy
@ManyToOne
private User createdBy;
@LastModifiedDate
private LocalDateTime modifiedAt;
}
When performing the insert query to the database, if some fields of the class are not given/ they are set as null in the query.
@DynamicInsert
public class User {
...
}
Before using @DynamicInsert
Hibernate:
insert
into
users
(password, username, id)
values
(?, ?, ?) // 141ms
After using @DynamicInsert
Hibernate:
insert
into
users
(username, id)
values
(?, ?) // 133ms