Advanced JPA 2

Sungju Kimยท2024๋…„ 10์›” 1์ผ

Sparta_Coding_Camp_TIL

๋ชฉ๋ก ๋ณด๊ธฐ
44/53

Auto generating key

๐Ÿ’ก When the key generation strategy is set to generationType.IDENTITY, the insert query cannot be delayed (์“ฐ๊ธฐ์ง€์—ฐ ์‚ฌ์šฉ๋ถˆ๊ฐ€). This is because generationType.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 the em.flush() or transaction.commit.

@Enumerated annotation

  • @Enumerated
    • Enum ๋งคํ•‘์šฉ๋„๋กœ ์“ฐ์ด๋ฉฐ ์‹ค๋ฌด์—์„œ๋Š” @Enumerated(EnumType.*STRING*) ์œผ๋กœ ์‚ฌ์šฉ๊ถŒ์žฅ
    • Default ํƒ€์ž…์ธ ORDINAL ์€ 0,1,2.. ๊ฐ’์œผ๋กœ ๋“ค์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ์ถ”ํ›„ ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€” ๊ฐ€๋Šฅ์„ฑ์žˆ๋‹ค.

Structure of JPA Repository

Examples of JPA Repository query

// 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();

More on Paging

// ์ฒซ ํŽ˜์ด์ง€ (ํŽ˜์ด์ง€ ์‚ฌ์ด์ฆˆ = 2)
Pageable firstPageWithTwoElements = PageRequest.of(0, 2);
// ๋‘๋ฒˆ์งธ ํŽ˜์ด์ง€ (ํŽ˜์ด์ง€ ์‚ฌ์ด์ฆˆ = 5)
Pageable secondPageWithFiveElements = PageRequest.of(1, 5);
// ํŽ˜์ด์ง€ ์‚ฌ์šฉ
List<Product> allTenDollarProducts =
productRepository.findAllByPrice(10, secondPageWithFiveElements);
  • Sorting
Page<Product> allProductsSortedByName = productRepository.findAll(Sort.by("name").accending());
  • Paging + Sorting
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")));
  • Methods of Paging
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> 

How to avoid using 'Optinal'

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));
    }
}

ifPresent

public 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>

  • Used in "load more" type paging.
  • Instead of the total number of elements, you can use the offset field for querying.
    • As a result, a count query is not generated, and a 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
}

JPQL

Examples

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);
}

QueryDSL

Key Features

  1. Type-Safe Queries:

    • QueryDSL ensures that your queries are checked at compile-time, meaning you can catch errors earlier compared to string-based queries (like JPQL or HQL).
    • If you refer to a field that doesn't exist or write an incorrect condition, the compiler will notify you, reducing runtime errors.
  2. Fluent API:

    • It provides a fluent API, which means you can chain methods together to create queries in a readable and intuitive way. For example:
      QPerson person = QPerson.person;
      queryFactory.selectFrom(person)
                  .where(person.name.eq("John"))
                  .fetch();
  3. Supports Multiple Backends:

    • QueryDSL works with JPA, SQL, MongoDB, Lucene, Hibernate, and other query languages.
    • It integrates seamlessly with the ORM framework (like JPA or Hibernate) that youโ€™re already using.
  4. Dynamic Queries:

    • QueryDSL makes it easier to create complex, dynamic queries based on user inputs or conditions, without having to build query strings manually.
    • For example, you can build queries conditionally based on inputs:
      if (name != null) {
          predicate = predicate.and(person.name.eq(name));
      }
      if (age != null) {
          predicate = predicate.and(person.age.eq(age));
      }
  5. Auto-generated Q Classes:

    • QueryDSL generates classes that represent the structure of your entities, known as Q classes (e.g., QPerson for a Person entity).
    • These classes provide direct access to fields and methods in a type-safe manner, replacing the need for string-based field names.

Example Use Case

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.

Dependencies Required in 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"

Reference Link

JPA Auditing

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;

}

@DynamicInsert

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
profile
Fully โœจcommittedโœจ developer, always eager to learn!

0๊ฐœ์˜ ๋Œ“๊ธ€