이전 포스트에는 회원가입을 단순 Memory로 구현했다.
이를 데이터베이스로 만들기 위한 방법을 이번 포스트에서 소개하겠다.
이전 프로젝트에서는 h2 database에 저장했지만, 지금은 더 보편적인 MySql database를 사용하기로 했다.
그리고 데이터베이스를 간단하게 다뤄주는 JPA API를 사용해보자 !
먼저 'build.gradle' 파일에 해당 JPA 와 MySQL dependency를 추가시키고 refresh 시켜서 라이브러리를 받아오자 !
implementation 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
그 후 properties에 다음을 추가한다.
물론 MySQL 정보또한 추가시킨다.
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mysql://localhost:3306/new_schema?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=1234
위 내용이 뭔지 모르겠다면 눌러 !! --> "해당 property에 관해 설명했던 포스트 링크"
추가적으로, spring.datasource.url에서
mysql://localhost:3306/member
이부분을 해석하자면 mysql을 사용할 것이고, 그 이후에는 IP주소:포트번호/데이터베이스(스키마이름) 순으로 작성한다.
앞에서 우리는 property 에서 "ddl-auto=none" 으로 자동 테이블 생성을 막았다.
그렇다면 따로 만들어야겠다.
우리에게 필요한건 단지 Id, Password 그리고 각 회원마다 보유되는 고유 Primary Key정보이다.
해당 정보가 다 들어가게 테이블을 만들자.
CREATE TABLE `websitedb`.`member` (
`id` INT NOT NULL,
`username` VARCHAR(45) NOT NULL,
`password` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`)
);
SQL workbench에서 단순 클릭만으로 이렇게 SQL 테이블문을 만들었다.
객체와 Relation DB를 Mapping 하는 것이 JPA의 역할이거늘, 이를 매핑해주기 위해 우리는 해당 DB와 연관된 Member 클래스에 "@Entity" 어노테이션 및 필요한 어노테이션들을 달아주자
package com.example.SpringAndReact.Domain;
import jakarta.persistence.*;
@Entity
public class Member {
@Id @GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
@Column(name="username") //db column 네임과 일치함
private String username;
@Column(name="password")
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
JPA는 EntityManager로 동작함을 다시한번 상기하자.
Spring Boot에서는 gradle을 통해 build되어질 때, 자동으로 EntityManager를 생성하고, 우리는 이를 가져다 쓴다.
추가로, Service에도 Transactional 어노테이션을 추가하는 것 잊지말자
package com.example.SpringAndReact.Repository;
import com.example.SpringAndReact.Domain.Member;
import jakarta.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaRepository implements MemberRepository{
private final EntityManager em;
public JpaRepository(EntityManager em) {
this.em = em;
}
@Override
public Member saveMember(Member member) {
em.persist(member);
return member;
}
@Override
public Optional<Member> findByUserId(String id) {
List<Member> result = em.createQuery("select m from Member m where m.username = :name", Member.class)
.setParameter("name", id)
.getResultList();
return result.stream().findAny();
}
}
findByUserId 메소드 설명
먼저 createQuery를 보자.
- Member.class는 반환 타입이다.
- :name 은 파라미터를 정의한 것이고, 이는 setParameter함수를 통해 연쇄적으로(메소드 체인) 파라미터 바인딩이 이뤄지고 있다.
EntityManager를 사용하므로 해당 객체를 가져와서 파라미터로 넘겨주자.
package com.example.SpringAndReact;
import com.example.SpringAndReact.Repository.MemoryMemberRepository;
import com.example.SpringAndReact.Service.MemberService;
import com.example.SpringAndReact.Service.MemberServiceImpl;
import com.example.SpringAndReact.controller.MemberController;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@PersistenceContext
private EntityManager em;
@Autowired
public AppConfig(EntityManager em){
this.em = em;
}
@Bean
public MemberService memberService (){
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
이 문제를 해결해보자!
알고보니 @Transactional 어노테이션을 인터페이스에 붙여서 생긴 문제였다.
클래스에 붙일것!!