1. 정의
스프링 어노테이션 Repository는 스프링 프레임워크에서 데이터 액세스를 처리하는 데 사용되는 어노테이션이다. Repository는 데이터베이스와의 상호작용을 추상화하고, 개발자가 데이터 액세스 코드를 간편하게 작성하고 관리할 수 있도록 도와준다.
스프링 어노테이션 Repository는 스프링 데이터 JPA와 함께 사용되는 경우가 많다. 스프링 데이터 JPA는 JPA(Java Persistence API)를 편리하게 사용할 수 있도록 지원하는 스프링 프레임워크의 모듈이다. Repository는 스프링 데이터 JPA에서 엔티티(Entity)에 대한 데이터 액세스를 처리하는 인터페이스로 사용된다.
Repository 인터페이스는 인터페이스로 정의되며, 스프링에 의해 런타임 시에 구현체가 자동으로 생성된다. Repository 인터페이스는 다양한 어노테이션을 사용하여 데이터 액세스에 대한 메소드들을 정의할 수 있다.
2. Repository의 자주 사용하는 어노테이션
1) @Repository
Repository 인터페이스에 적용되는 어노테이션으로, 스프링에게 해당 인터페이스를 Repository로 인식하도록 한다.
2) @Query
데이터베이스에 직접 쿼리를 작성할 때 사용하는 어노테이션이다. JPA의 NamedQuery나 NativeQuery를 사용하여 쿼리를 정의하고 실행할 수 있다.
3) @Param
쿼리에 파라미터를 전달할 때 사용하는 어노테이션이다. 메소드의 파라미터와 쿼리의 파라미터를 매핑해주는 역할을 한다.
4) @Transactional
데이터베이스 트랜잭션을 관리할 때 사용하는 어노테이션이다. Repository 메소드에 @Transactional 어노테이션을 추가하면, 해당 메소드가 트랜잭션 단위로 실행되도록 설정할 수 있다.
5) Entity 관련 어노테이션(기본)
@Entity, @Table, @Id, @GeneratedValue, @Column 등의 JPA 관련 어노테이션들을 함께 사용하여 엔티티의 매핑 정보를 정의하고 데이터베이스와의 매핑을 처리할 수 있다.
6) Entity 관련 어노테이션(관계설정)
@JoinColumn
- @ManyToOne, @OneToOne 어노테이션과 함께 사용된다.
- 조인 컬럼을 지정할 때 사용되며, 조인 컬럼은 연관 관계를 맺는 두 테이블 간의 외래 키(Foreign Key)를 의미한다.
- @JoinColumn 어노테이션은 조인 컬럼에 대한 매핑 정보를 제공한다.
@ManyToOne
- 다대일(Many-to-One) 관계를 매핑할 때 사용된다.
- 예시로, 게시글과 작성자 테이블이 있을 때, 하나의 작성자는 여러 개의 게시글을 작성할 수 있다.
- @ManyToOne 어노테이션을 이용하여 게시글과 작성자 사이의 다대일 관계를 매핑할 수 있다.
@ManyToMany
- 다대다(Many-to-Many) 관계를 매핑할 때 사용된다.
- 예를 들어 학생과 과목 테이블이 있을 때, 하나의 학생은 여러 개의 과목을 수강할 수 있으며, 하나의 과목은 여러 명의 학생이 수강할 수 있다.
- @ManyToMany 어노테이션을 이용하여 학생과 과목 사이의 다대다 관계를 매핑할 수 있다.
@OneToOne
- 일대일(One-to-One) 관계를 매핑할 때 사용된다.
- 예를 들어 회원과 회원 프로필 테이블이 있을 때, 하나의 회원은 하나의 프로필을 가질 수 있다.
- @OneToOne 어노테이션을 이용하여 회원과 회원 프로필 사이의 일대일 관계를 매핑할 수 있다.
@OneToMany
- 일대다(One-to-Many) 관계를 매핑할 때 사용된다.
- 예를 들어 부서와 직원 테이블이 있을 때, 하나의 부서는 여러 명의 직원을 가질 수 있다.
- @OneToMany 어노테이션을 이용하여 부서와 직원 사이의 일대다 관계를 매핑할 수 있다.
@ToString.Exclude
- toString() 메서드를 자동으로 생성할 때, 해당 필드를 출력하지 않도록 설정할 수 있다.
- Entity 관계를 양방향으로 설정할 때, 무한루프 빠지는 현상 방지하기 위해 사용하기도 한다.
@Builder.Default
- 빌더 패턴을 이용하여 객체를 생성할 때, 기본값을 설정할 수 있다.
3. Repository의 자주 사용하는 메소드
1) save()
데이터를 저장하거나 업데이트하는 메소드로, 새로운 엔티티를 저장하거나 기존의 엔티티를 업데이트할 때 사용한다.
JPA의 persist()와 merge() 메소드를 대체하는 역할을 수행한다. 저장 또는 업데이트된 엔티티는 영속성 컨텍스트(Persistence Context)에 관리되며, 트랜잭션이 commit되는 시점에 DB에 반영된다.
2) findById()
주어진 ID를 사용하여 엔티티를 조회하는 메소드로, 데이터베이스에서 해당 ID에 해당하는 엔티티를 조회하여 반환한다.
3) findAll()
모든 엔티티를 조회하는 메소드로, 데이터베이스에 있는 모든 엔티티를 조회 후 리스트로 반환한다.
4) deleteById()
주어진 ID를 사용하여 엔티티를 삭제하는 메소드로, 데이터베이스에서 해당 ID에 해당하는 엔티티를 삭제한다.
5) delete()
주어진 엔티티를 삭제하는 메소드로, 엔티티를 기준으로 데이터베이스에서 해당 엔티티를 삭제한다.
6) count()
엔티티의 개수를 조회하는 메소드로, 데이터베이스에 있는 엔티티의 총 개수를 반환한다.
7) findBy{PropertyName}()
엔티티의 특정 프로퍼티 값을 기준으로 조회하는 메소드로, 프로퍼티 이름을 기준으로 메소드를 정의하고, 해당 프로퍼티의 값을 사용하여 엔티티를 조회할 수 있다.
예를 들어, findByUsername(String username)과 같이 메소드를 정의하여 username 프로퍼티 값을 기준으로 엔티티를 조회할 수 있다.
Tip> Repository 내 메소드 생성: 필드명 카멜표기법 적용! 추상메소드로 정의!
1) findBy필드명 : 해당 필드로 엔티티 검색
2) findBy필드명And필드명: 해당 필드 조건 2개 모두 true인 경우, 엔티티 검색
3) findBy필드명Or필드명: 해당 필드 조건 1개 이상 true인 경우, 엔티티 검색
4) existsBy필드명: 해당 필드 조건의 엔티티 존재 여부
5) existsBy필드명And필드명: 해당 필드 조건 2개 모두 존재할 경우, true 반환
6) existsBy필드명Or필드명: 해당 필드 조건 1개 이상 존재할 경우, true 반환
//------------------------------------------------------- Repository 내 메소드 생성 예시
@Repository
public interface MemberEntityRepository extends JpaRepository<MemberEntity, Integer> {
// 1. 추가 생성 메소드 (기능: 이메일 정보로 엔티티 찾기)
MemberEntity findBymemail(String memail);
// sql: select * from member where memail =?;
// 2. 추가 생성 메소드 (기능: 해당 이메일과 비밀번호가 일치한 엔티티 찾기)
Optional<MemberEntity> findByMemailAndMpassword( String memail , String mpassword);
// sql: select * from member where memail = ? and mpassword =?;
// 3. 중복체크 활용 (동일한 이메일 있을 경우: true, 없을 경우: false)
boolean existsByMemail( String memail);
// 4. 만약에 동일한 이메일과 패스워드가 존재하면 true, 아니면 false
boolean existsByMemailAndMpassword( String memail, String mpassword);
// 5. 아이디 찾기
Optional<MemberEntity> findByMnameAndMphone(String mname, String mphone);
// 6. 비밀번호 찾기 (query 직접사용)
boolean existsByMemailAndMphone(String memail, String mphone);
}
4. 코드로 확인
1) Dto, Entity
// ---------------------------------------------------------- NoteDto
package test.day03;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data // getter, setter, toString 제공
@AllArgsConstructor@NoArgsConstructor@Builder
public class NoteDto {
private int noteNumber;
private String noteContent;
// Dto -> Entity 변경작업 (이유: Service에서 사용하기 위함)
public NoteEntity toEntity(){
return NoteEntity.builder()
.noteNumber(this.noteNumber)
.noteContent(this.noteContent)
.build();
}
}
// ---------------------------------------------------------- NoteEntity
package test.day03;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity // 테이블과 해당 클래스 객체간 매핑[연결]
@Table(name = "note")
@Data
@AllArgsConstructor@NoArgsConstructor@Builder
public class NoteEntity { // 클래스 = 테이블, 객체 = 레코드
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int noteNumber;
@Column
private String noteContent;
// entity -> dto 타입으로 변경작업
// 목적: entity자체로 이동시키면 DB 데이터 손상이 있을 수 있기 때문에 안전성을 위해 Dto로 변환하여 사용
public NoteDto toDto() {
return NoteDto.builder()
.noteNumber(this.noteNumber)
.noteContent(this.noteContent)
.build();
}
}
2) Repository, Service
// ---------------------------------------------------------- NoteEntityRepository
package test.day03;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository // Entity 조작 인터페이스 + DB와 소통
public interface NoteEntityRepository extends JpaRepository<NoteEntity, Integer> {
}
// ---------------------------------------------------------- NoteService
package test.day03;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
// 비지니스 로직(실직적인 업무) 담당
@Slf4j
@Service
public class NoteService {
@Autowired
NoteEntityRepository noteEntityRepository;
// 해석: 빈에 NoteEntityRepository 생성자 주입
// 1. 쓰기
@Transactional
public boolean write(NoteDto noteDto) {
log.info("service write in " + noteDto);
NoteEntity entity = noteEntityRepository.save(noteDto.toEntity());
// 해석: save() JSPWEB에서 사용하던 insert와 동일한 역할
if( entity.getNoteNumber() >= 0 ){
return true;
}
//
return false;
}
// 동작 원리
// 1) write 메소드는 매개값을 NoteDto type의 noteDto 인스턴스를 받는다.
// 2) 들어온 noteDto 내용을 log로 기록을 남긴다.
// 3) noteDto에 생성한 toEntity 메소드를 이용하여 Dto를 Entity 형태로 변환한다.
// 4) 변환된 Entity 형태로 noteEntityRepository에 저장한다.
// 5) NoteEntityRepository DB에 내용을 저장한다. (JPA 이용: application.properties에 관련 내용 저장)
// 6) 저장 완료된 값을 NoteEntity 타입의 entity 인스턴스에 초기화한다.
// 7) 정상적으로 DB에 Insert 되면, NoteNumber 값이 0보다 크기 때문에, 이 경우 True 반환 아니면 false 반환
// 2. 출력
@Transactional
public ArrayList<NoteDto> read() {
log.info("service read");
List<NoteEntity> entityList = noteEntityRepository.findAll();
// 해석1: noteEntityRepository에 저장되어 NoteEntity 인스턴스 모두 찾아서 entityList List에 저장
// 해석2: findAll() JSPWEB에서 사용하던 select와 동일한 역할
ArrayList<NoteDto> noteDtoList = new ArrayList<>();
// 해석: NoteDto를 취급하는 ArrayList 생성 (ArrayList name: noteDtoList)
entityList.forEach( e -> {
noteDtoList.add(e.toDto());
});
// 해석1: entityList의 index 0부터 마지막 번호까지 반복문 수행
// 해석2: NoteEntity에 생성한 toDto 메소드를 이용하여 Dto 형태로 변환하여 noteDtoList에 저장
return noteDtoList;
// 해석: Dto를 저장한 ArrayList noteDtoList 반환
}
// 3. 삭제
@Transactional
public boolean delete(int noteNumber) {
log.info("service delete in " + noteNumber);
Optional<NoteEntity> optionalNoteEntity = noteEntityRepository.findById(noteNumber);
// 해석1: Optional Wrapper Class로, null을 포함하고 있음 (null check 가능)
// 해석2: noteNumber에 해당되는 데이터를 Optional Wrapper class에 저장
// 해석3: findById() JSPWEB에서 사용하던 select와 동일한 역할
boolean checkNull = optionalNoteEntity.isPresent();
// 해석: 받아온 optionalNoteEntity null 여부 확인 (null: false, Entity: true)
if( checkNull ){ // true이면 아래 구문 실행
NoteEntity noteEntity = optionalNoteEntity.get();
// 해석: [Wrapper class 분해 작업] entity 존재하면 noteEntity 인스턴스 초기화
noteEntityRepository.delete(noteEntity);
// 해석1: noteEntity 삭제
// 해석2: delete() JSPWEB에서 사용하던 delete와 동일한 역할
return true;
}
return false;
}
// 4. 수정 (@Transactional 필수 사용)
@Transactional // import javax.transaction.Transactional
public boolean update(NoteDto noteDto) {
log.info("service update in " + noteDto);
Optional<NoteEntity> optionalNoteEntity = noteEntityRepository.findById(noteDto.getNoteNumber());
if( optionalNoteEntity.isPresent() ){ // delete에서 사용한 코드 간소화
NoteEntity noteEntity = optionalNoteEntity.get();
noteEntity.setNoteContent( noteDto.getNoteContent() );
// 해석: noteEntity 인스턴스의 필드 변경 = 해당 레코드 값 변경
// 특징1: Entity java 클래스 = db 테이블, java 객체(인스턴스) = db 레코드
// 특징2: JPA 메소드가 별도 존재하지 않기 때문에 @Transactional을 사용
// @Transactional을 선언한 메소드 내 엔티티객체 필드 변화가 있을 경우, 실시간으로 commit 처리해주는 역할
return true;
}
return false;
}
}
3) Controller
package test.day03;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
@Slf4j
@RestController
@RequestMapping("/note")
public class NoteController {
@Autowired
NoteService noteService;
// -------------------------------- RESTful API ----------------------------------
// 1. 쓰기
@PostMapping("/write")
public boolean writeNote( @RequestBody NoteDto noteDto ){
log.info("writeNote in: ", noteDto);
boolean result = noteService.write(noteDto);
return result;
}
// 2. 출력
@GetMapping("/read")
public ArrayList<NoteDto> readNote(){
log.info("readNote");
ArrayList<NoteDto> result = noteService.read();
return result;
}
// 3. 삭제
@GetMapping("/delete")
public boolean deleteNote( @RequestParam int noteNumber ){
log.info("deleteNote in: ", noteNumber);
boolean result = noteService.delete(noteNumber);
return result;
}
// 4. 수정
@PutMapping("/update")
public boolean updateNote( @RequestBody NoteDto noteDto ){
log.info("updateNote in: ", noteDto);
boolean result = noteService.update(noteDto);
return result;
}
}
5. 흐름 이해도
*Dto를 가지고 Service에서 Repository에 접근할 때는 Entity로 변환하여 전송하고, Controller에 Entity를 전달할 때는 Dto로 변환하여 전달한다.
'SpringBoot' 카테고리의 다른 글
SpringBoot - @RequestMapping 활용 (0) | 2023.04.28 |
---|---|
SpringBoot - @Controller, @RestController 비교 (0) | 2023.04.28 |
SpringBoot - @ComponentScan 이해 (0) | 2023.04.28 |
SpringBoot - 주요 어노테이션 (0) | 2023.04.28 |
SpringBoot 용어 및 기분 구조 (0) | 2023.04.28 |