서론
서버
구조
패턴별 분석
보완 해야 되는 부분 & 깨달은 부분
이 서버에서는 Mysql 데이터베이스와 연동시켜 데이터를 영속성하게 관리해보는 첫 번째 서버이다.
Mysql기준 자바와 Mysql서버를 연동시키기 위해 필요한 준비
1. Gradle 라이브러리 추가
//jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//mysql드라이버
implementation 'mysql:mysql-connector-java:8.0.28'
spring.datasource.url=jdbc:mysql://localhost:3306/books //db주소
spring.datasource.username={유저이름} //접속할 유저이름
spring.datasource.password={비밀번호} //DB접속 비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver //사용할 JDBC 드라이버 gradle에 의해서 설치됨
spring.jpa.hibernate.ddl-auto=update //db에 update를 진행
spring.jpa.properties.hibernate.show_sql=true //SQL쿼리문 콘솔에 출력 여부
spring.jpa.properties.hibernate.format_sql=true //SQL쿼리 포맷팅
spring.jpa.properties.hibernate.use_sql_comments=true //SQL쿼리 주석 포함 여부
spring.jpa.open-in-view=false //잘 모르겠음
책 등록, 회원 등록이 가능하고
각 회원이 책을 대출 받거나 반납 할 수 있다.
1. 회원이 선택한 책을 대출 받을 수 있다.
2. 회원이 대출한 책을 반납하지 않았다면 다른 책을 대출 받을 수 없다.
3. 회원이 선택한 책이 대출중이라면 대출 받을 수 없다.
4. 대출에 관한 내용들은 삭제되지 않고 대출해준날짜, 돌려받은 날짜로 영구 보관한다.
책 테이블
회원 테이블
대출 테이블
이렇게 3개의 테이블 구성하여 작업을 진행했다.

위 프로젝트 구조 처럼 3개의 컨트롤러, 서비스, 레포지토리로 역할분담을 시켰다.
각 컨트롤러가 맞은 역할은 다음과 같다.
BooksController
1. 책 등록 POST
2. 선택한 책 조회 READ
3. 모든 책 조회 READ
MemberController
1. 회원 등록 POST
LoanController
1. 선택한 회원의 책 대출 정보 조회 READ
2. 선택한 회원의 선택한 책 대출 등록 POST
3. 선택한 회원의 선택한 책 대출 반납 PUT
공통적으로 API에 대한 컨트롤러라서 RestController로 스프링 컨테이너에 등록시켰다.
모델에서는
1. 요청, 응답에 맞게 작업한 DTO 패키지
2. DB와의 매핑을 위한 entity 패키지
3. DB의 접근을 위한 repository 패키지
이렇게 구성되어있는데
Dto에는 getter가 필요하다.
requestDto -> entity
responseDto -> json화
위 두 과정에서 모두 자바에서 getter를 통해 필드값을 가져가기 때문
@Entity
어노테이션으로 스프링 등록
@Table(name = "테이블이름")
name을 생략하게 되면 클래스 명을 따라감
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Id어노테이션으로 PK선언 및 자동 증가
@Column(name = "book_id")
컬럼명을 정할 수 있고 여러 설정 부여 가능 ex)nullable
그리고 필드명을 정할때 언더바 _ 를 넣게되면 오류가 발생한다.
@Column(name = "registed_date")
private Date registedDate;
어노테이션을 통해 이름에 _를 부여하는 것은 괜찮지만 필드명은 카멜케이스를 지켜야 한다.
@ManyToOne
@JoinColumn(name = "book_id", referencedColumnName = "book_id")
private Book book;
@ManyToOne
@JoinColumn(name = "member_id", referencedColumnName = "member_id")
private Member member;
아직 익숙치 않은 단어인것 같다.
현재 loan_book테이블 기준으로 book_id와 member_id는 외래키이다.
똑같은 book_id, member_id의 값이 여러개 존재 할 수 있다.
@ManyToOne
나는 이곳에서 동일한 값을 여러개 받을 수 있어
@OneToMany
나는 이곳에서 유일하지만 다른 곳은 여러번 갈 수 있어
로 이해하고 있다.
3개의 레포지토리 모두 JpaRepositry 인터페이스를 상속받아서 사용하고 구현체로
SimpleJpaRepository를 선택하여 사용되고 있다.
이 중에서 LoanRepository는 조금 예외로 @query 어노테이션을 사용하여 쿼리문을 사용했다.
public interface LoanRepository extends JpaRepository<LoanBooks, Long> {
@Query("SELECT NEW com.example.javaspringlv2.model.dto.LoanResponseDto(m.name, m.phoneNumber, b.title, b.author, lb.isReturned, lb.loanDate) " +
"FROM LoanBooks lb " +
"JOIN lb.member m " +
"JOIN lb.book b " +
"WHERE m.memberId = :memberId")
List<LoanResponseDto> findByMemberMemberId(Long memberId);
@Query("SELECT lb FROM LoanBooks lb " +
"WHERE (lb.member.memberId = :memberId OR lb.book.bookId = :bookId) AND lb.isReturned = false")
Optional<LoanBooks> findByMemberIdOrBookIdAndIsNotReturned(Long memberId, Long bookId);
}
우선 첫번째 쿼리의 경우 먼저 해당 유저의 대출 정보를 찾기 위해 loan_table에서 memberId에 매칭되는 데이터를 찾아오는 과정이다.
또한 그 조회된 내용에 매칭되게 유저의이름, 전화번호, 책제목, 책저자등을 다른 테이블에서 가져와야 했기 때문에 저렇게 구현해봤다.
다른 방법이 존재하는지는 잘 모르겠다.
두번째 쿼리의 경우 해당 유저가 현재 도서 대출을 받을 수 있는 상황인지 판별하기 위해
유저 id, 책 id 둘 중 아무거나 매칭되는 것 들 중에 반납이 아직 안된 정보를 찾는 쿼리문이다.
이것 또한 쿼리문을 사용하지 않고 다르게 구현하는 방법이 있다면 찾아봐야 겠다.
Service
서비스의 경우 Optional을 통해 실패의 경우를 대비했고,
JpaRespositroy에 존재하지 않는 메소드를 만들어서 사용한 경우(loanRepository)는
@Transactional어노테이션을 등록하여 트랜잭션널한 처리를 하는 메소드임을 스프링에 등록했다.
현재 메소드들은 내부에서 예외처리를 순차적으로 체크하며 진행하기 때문에 @Transactional이 없어도 상관이 없지만 좀 수정을 해봐야 겠다.