iBatis나 MyBatis 등에서 Dao라고 불리는 DB Layer 접근자로, JPA에서는 Repository라고 부르며 인터페이스
로 생성한다
인터페이스 생성 후, JpaRepository<Entity 클래스, PK 타입>을 상속하면
기본적인 CRUD 메소드가 자동으로 생성된다.
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostsRepository extends JpaRepository<Users, Long> {
}
save(), findById(), existsById(), count(), deleteById(), delete(), deleteAll()
findOne(), findAll(), count(), exists()
Optional<Users> findById(Long id);
와 같은 형태로 메소드를 커스텀할 수 있다import org.springframework.data.jpa.repository.JpaRepository;
public interface PostsRepository extends JpaRepository<Users, Long> {
Optional<Users> findById(Long id);
}
<T>
란?Java8에서부터 나타났으며 null이 올 수 있는 값을 감싸는 Wrapper 클래스다.
Optional을 사용하면 참조하더라도 NullPointException
이 발생하지 않도록 도와주기 때문에 null검사를 위한 코드를 줄여, 코드를 간결하게 만들 수 있다.
public final class Optional<T> {
// If non-null, the value; if null, indicates no value is present
private final T value;
...
}
링크 참고 - [Java] 언제 Optional을 사용해야 하는가? 올바른 Optional 사용법 가이드 - (2/2)
Optional은 Wrapper 클래스이기 때문에 값이 없을 수도 있는데, 이때는 Optional.empty()
로 생성할 수 있다.
Optional<String> optional = Optional.empty();
System.out.println(optional); // Optional.empty
System.out.println(optional.isPresent()); // false
Optional 클래스는 내부에서 static 변수로 EMPTY 객체를 미리 생성해서 가지고 있다. 이러한 이유로 빈 객체를 여러 번 생성해줘야 하는 경우에도 1개의 EMPTY 객체를 공유함으로써 메모리를 절약하고 있다.
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
...
}
어떤 데이터가 절대 Null이 아니라면 Optional.of()
로 생성할 수 있다.
만약 Optional.of()로 Null을 저장하려고 하면 NullPointerException
이 발생한다.
// Optional의 value는 절대 null이 아니다.
Optional<String> optional = Optional.of("MyName");
만약 어떤 데이터가 null이 올 수도 있고 아닐 수도 있는 경우에는 Optional.ofNullable()
로 생성할 수 있다.
그리고 이후에 orElse
또는 orElseGet
메소드를 이용해서 값이 없는 경우라도 안전하게 값을 가져올 수 있다.
String isNull;
String name;
isNull = "loose";
name = Optional.ofNullable(isNull).orElse("test");
System.out.println(name); //isNull값이 null이 아니므로 "loose" 출력
isNull = null;
name = Optional.ofNullable(isNull).orElse("test");
System.out.println(name); //isNull값이 null이므로 "test" 출력
기본적으로 orElse~는 Optional에 올 값이 null인 경우 orElse~ 안에 있는 내용을 실행시킨다
=> orElse~는 if문을 이용해 처리하는 명령어를 짧게 람다식처럼 처리 가능
orElse~는 Spring Data JPA에서 가장 잘 사용된다.
Optional<User> user = userRepository.findById(id); //정상
User user = userRepository.findById(id); //에러
Spring Data JPA 메소드는 return 값이 Optional 로 이루어져있다.
그러므로 위처럼 Optional 타입으로 받아야 정상 처리된다.
String name1 = Optional.ofNullable("test1").orElse("test2"); // 맞음
Optional name1 = Optional.ofNullable("test1").orElse("test2"); // 틀림
하지만 orElse~를 사용하는 경우 null이 아닐 시 Optional이 아닌 Optional의 인자가 반환된다
String name1 = Optional.ofNullable("loose").orElse("test");
String name2 = Optional.ofNullable("loose").orElseGet(() -> "test");
System.out.println("name1 = " + name1);
System.out.println("name2 = " + name2);
//출력 결과
//name1 = loose
//name2 = loose
orElse는 매개변수가 고정 값일 때 사용하고 orElseGet은 매개변수가 메소드일 때 사용하는 것이 좋다.
orElse가 orElseGet에 비해 좋은 장점
위의 장점처럼 orElse는 orElseGet에 비해 장점이 크지 않아서 대부분의 상황에선 orElseGet을 사용하기도 한다.
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("user doesn't exist");
orElseThrow는 Optional의 인자가 null일 경우 예외처리를 시킨다.
//lambda 이용
List<String> nameList = Optional.ofNullable(getNames())
.orElseGet(() -> new ArrayList<>());
getNames()로 값을 가져왔을 때, null인 경우 .orElseGet()가 실행되고, 람다 함수를 통해 nameList가 새로 만들어진다.
예를 들어 아래와 같은 우편번호를 꺼내는 null 검사 코드가 있다고 하자. 이 코드는 null 검사 때문에 상당히 복잡하다.
public String findPostCode() {
UserVO userVO = getUser();
if (userVO != null) {
Address address = user.getAddress();
if (address != null) {
String postCode = address.getPostCode();
if (postCode != null) {
return postCode;
}
}
}
return "우편번호 없음";
}
하지만 Optional을 사용하면 이러한 코드를 아래와 같이 표현할 수 있다.
public String findPostCode() {
// 위의 코드를 Optional로 펼쳐놓으면 아래와 같다.
Optional<UserVO> userVO = Optional.ofNullable(getUser());
Optional<Address> address = userVO.map(UserVO::getAddress);
Optional<String> postCode = address.map(Address::getPostCode);
String result = postCode.orElse("우편번호 없음");
// 그리고 위의 코드를 다음과 같이 축약해서 쓸 수 있다.
String result = user.map(UserVO::getAddress)
.map(Address::getPostCode)
.orElse("우편번호 없음");
}
예를 들어 아래와 같이 이름을 대문자로 변경하는 코드에서 NPE 처리를 해준다고 하자.
String name = getName();
String result = "";
try {
result = name.toUpperCase();
} catch (NullPointerException e) {
throw new CustomUpperCaseException();
}
위의 코드는 다소 번잡하고 가독성이 떨어지는데 이를 Optional를 활용하면 아래와 같이 표현할 수 있다.
Optional<String> nameOpt = Optional.ofNullable(getName());
String result = nameOpt.orElseThrow(CustomUpperCaseExcpetion::new)
.toUpperCase();
참고
[Spring JPA]JpaRepository 기본 사용법
[Java] Optional이란? Optional 개념 및 사용법 - (1/2)
Optional의 orElse, orElseGet, orElseThrow 사용법