Java - Optional

murphytklee·2023년 4월 24일
0
post-thumbnail

ChatGPT로 공부하는 Optional

2. Optional

2.1 Optional 개념

❓ Java 에서 Optional이란?

  • 자바에서의 Optional은 Java 8에서 도입된 클래스로, 값이 없는 상황을 나타내기 위한 방법이다.
  • Optional은 자바 프로그램에서 흔히 발생하는 null 포인터 예외를 방지하기 위해 설계되었다.
  • Optional을 사용하면 코드의 가독성을 향상시키고 null 포인터 예외의 위험을 줄일 수 있으며, 값이 없는 경우를 명확하게 표시하고 프로그래머가 해당 경우를 명시적으로 처리하도록 강제할 수 있다.
  • 그러나 Optional을 지나치게 사용하지 않고 적절하게 사용하는 것이 중요하다, 경우에 따라 코드에 불필요한 복잡성을 추가할 수 있다.

❓ Optional은 항상 Generic 형태인가?

  • 예, Optional은 항상 Generic 형태로 사용됩니다. Optional
  • Optional 클래스는 Java에서 제네릭(Generic)을 사용하여 구현되었기 때문에, 값을 감싸는 래퍼 클래스인 Optional도 항상 Generic 형태로 사용됩니다.


2.2 Optional 사용예시

❓ Optional 사용 예시

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        String value = "Hello, World!"; // 값이 있는 경우
        Optional<String> optionalValue = Optional.of(value); // Optional 객체 생성

        // 값이 있는 경우
        if (optionalValue.isPresent()) {
            System.out.println("Value is present: " + optionalValue.get());
        }

        // 값이 없는 경우
        Optional<String> emptyOptional = Optional.empty(); // 빈 Optional 객체 생성
        String defaultValue = "Default Value";
        String result = emptyOptional.orElse(defaultValue); // 값이 없을 경우 기본 값 제공
        System.out.println("Result: " + result);

        // 값이 없는 경우 작업 수행
        emptyOptional.ifPresent(val -> System.out.println("Value is absent"));

        // 값 변환
        Optional<Integer> lengthOptional = optionalValue.map(String::length); // 문자열 길이 변환
        lengthOptional.ifPresent(len -> System.out.println("Length: " + len));
    }
}

❓ Spring에서 Optional 은?

  • Spring 프레임워크는 자바의 Optional을 활용하여 메소드의 반환 값이나 메소드의 파라미터에 대해 값이 없는 경우를 처리하는 방법을 제공합니다. 아래는 Spring에서 Optional이 사용되는 몇 가지 예시입니다.
  1. 메소드의 반환 값으로 Optional 사용:
    메소드의 반환 값이 항상 값이 없을 수 있는 경우, Optional을 사용하여 명시적으로 값이 없음을 표현할 수 있습니다. 이는 null 값을 반환하는 대신 Optional.empty()를 반환하도록 설계된 메소드에서 많이 사용됩니다.
public Optional<User> findUserById(Long userId) {
    // userId에 해당하는 User를 찾아서 반환, 값이 없는 경우 Optional.empty() 반환
    // ...
}

  1. 메소드의 파라미터로 Optional 사용:
    메소드의 파라미터 값이 선택적으로 제공될 수 있는 경우, Optional을 사용하여 값이 없을 수 있음을 명시할 수 있습니다. 이를 통해 파라미터 값이 없는 경우에 대한 처리를 명확하게 할 수 있습니다.
public void updateUser(Optional<Long> userId, String name) {
    // userId가 제공되었을 경우 해당 userId의 User 정보를 업데이트, 값이 없는 경우 무시
    // ...
}

  1. JPA와의 연동에서의 Optional 사용:
    Spring Data JPA에서는 JPA 엔티티의 필드에 대해 Optional을 사용하여 값이 없을 수 있는 경우를 표현할 수 있습니다. 이를 통해 JPA의 엔티티 필드에 null 값을 허용하지 않고, Optional을 사용하여 값을 지정하지 않은 경우를 처리할 수 있습니다.
@Entity
public class User {
    @Id
    private Long id;

    private String name;

    private Optional<String> email; // email이 선택적으로 제공될 수 있는 경우
    // ...
}

❓ Repository Interface에서 JpaRepository를 extends하며 Optional을 사용하는 경우

  • Spring Data JPA에서 JpaRepository 인터페이스를 상속하여 Repository 인터페이스를 구현할 때, 메소드의 반환 값이나 파라미터에 Optional을 사용하여 값이 없는 경우를 처리할 수 있습니다.
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    // Optional을 사용하여 값이 없는 경우 처리
    Optional<User> findByUsername(String username); 
    
		// 여러 개의 파라미터에 대한 Optional 사용
    Optional<User> findByIdAndIsActive(Long userId, boolean isActive); 
    
		// Optional을 사용하지 않고 값이 없는 경우 처리
    void deleteByUsername(String username); 
    
}

❓ findBy 와 그 외의 메서드같은 경우는 누가 제공해주고있지 ?
JpaRepository ? Spring boot ? Optional ?

  • findBy와 같은 메소드는 Spring Data JPA가 제공하는 기능입니다.
  • JpaRepository는 Spring Data JPA에서 제공하는 인터페이스 중 하나로, JPA(Java Persistence API)를 기반으로 데이터베이스와 상호작용하는 기능을 제공하는 인터페이스입니다.

❓ Spring Data JPA가 제공하는 기능들의 종류?

  • Spring Data Jpa 공식문서

  • 5.1.3. Query Methods

    KeywordSampleJPQL snippet
    DistinctfindDistinctByLastnameAndFirstnameselect distinct …​ where x.lastname = ?1 and x.firstname = ?2
    AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
    OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
    Is, EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
    BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
    LessThanfindByAgeLessThan… where x.age < ?1
    LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
    GreaterThanfindByAgeGreaterThan… where x.age > ?1
    GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
    AfterfindByStartDateAfter… where x.startDate > ?1
    BeforefindByStartDateBefore… where x.startDate < ?1
    IsNull, NullfindByAge(Is)Null… where x.age is null
    IsNotNull, NotNullfindByAge(Is)NotNull… where x.age not null
    LikefindByFirstnameLike… where x.firstname like ?1
    NotLikefindByFirstnameNotLike… where x.firstname not like ?1
    StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
    EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
    ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
    OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
    NotfindByLastnameNot… where x.lastname <> ?1
    InfindByAgeIn(Collection ages)… where x.age in ?1
    NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1
    TruefindByActiveTrue()… where x.active = true
    FalsefindByActiveFalse()… where x.active = false
    IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstname) = UPPER(?1)

❓ JpaRepository를 extends하지 않으면 쓸수 없나 ?

  • JpaRepository를 상속하지 않아도, 별도의 Repository 인터페이스를 구현하고, 해당 인터페이스를 빈(Bean) 객체로 등록하여 사용할 수 있습니다.
  • 이렇게 직접 구현한 Repository 인터페이스에는 JpaRepository에서 제공하는 메소드들을 직접 구현하여 사용할 수 있습니다.
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import java.util.Optional;

public interface UserRepository extends Repository<User, Long> {
    
		// 직접 구현한 username을 기준으로 User 엔티티 조회
    @Query("SELECT u FROM User u WHERE u.username = ?1")
    Optional<User> findByUsername(String username); 
    
}

❓ JPA Repository를 상속받고도 @Query를 통해 구현할 수 있어 ?

  • Spring Data JPA의 JpaRepository를 상속받아도, @Query 어노테이션을 통해 직접 쿼리를 작성하여 Repository 메소드를 구현할 수 있습니다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    @Query("SELECT u FROM User u WHERE u.username = :username")
    Optional<User> findByUsername(@Param("username") String username);
}

❓ JPA를 extends 하지 않고 직접 Repository 인터페이스를 구현하고 사용하려면 꼭 Query를 사용해야 하나?

  • 아니요, @Query 어노테이션을 사용하지 않아도 Repository 인터페이스를 구현하여 메소드를 직접 구현하고 사용할 수 있습니다.
  • Spring Data JPA에서는 Repository 인터페이스를 구현하고, 해당 인터페이스를 빈(Bean)으로 등록하여 사용할 수 있습니다. 이때 @Repository 어노테이션을 사용하여 Repository 인터페이스를 구현한 클래스를 스프링에게 알려줄 수 있습니다. 그리고 해당 클래스에서 원하는 메소드를 직접 구현하여 사용할 수 있습니다.
@Repository
public class UserRepositoryImpl implements UserRepository {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Optional<User> findByUsername(String username) {
        // 직접 원하는 로직을 구현
        // 예시: JPQL 쿼리를 사용하여 사용자를 조회하고 Optional로 반환
        return entityManager.createQuery("SELECT u FROM User u WHERE u.username = :username", User.class)
                .setParameter("username", username)
                .getResultList()
                .stream()
                .findFirst();
    }
}

2.3 과제에서의 Optional

  • BoardRepository
package com.sparta.spring_lv1_assignment.Repository;

import com.sparta.spring_lv1_assignment.entity.Board;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

//Spring JPA -> interface 끼리의 상속은 extends
public interface BoardRepository extends JpaRepository<Board, Long> { // <레포지토리랑 연결할 테이블 클래스, id 타입>
    // spring jpa 에서는 id로 조회하는 것 밖에 제공되지 않기 때문에
    // 특정 게시글의 제목이나 내용으로 검색하기 위해서 query method 를 사용

    Optional<Board> findByBoardTitle(String boardTitle);
    Optional<Board> findByBoardIdAndUsername(Long boardId, String username);
}
  • BoardService
// 게시글 수정
    @Transactional
    public BoardResponseDto updateBoard(BoardRequestDto.Update updateDto, HttpServletRequest request) {
        // 지금 요청중인 사용자의 정보
        User user = checktoken(request);
        // 요청중인 게시글의 정보
        Board board = checkBoard(updateDto.getBoardId());

        if (user.getRole() == UserRoleEnum.***USER***){
            boardRepository.findByBoardIdAndUsername(updateDto.getBoardId(), user.getUsername()).orElseThrow(
                    () -> new NoSuchElementException("본인의 글만 수정할 수 있습니다.")
            );
        }
        board.update(updateDto);
        return new BoardResponseDto(board);
    }

0개의 댓글