본문 바로가기

Dev.BackEnd/Spring Boot

#SLiPP Spring boot, JPA 강의 - 반복주기 3


이 포스팅은 다음 강의를 바탕으로 작성되었습니다.

>> SLiPP 자바 웹 애플리케이션 개발 >>

반복주기3

내용
1. H2 데이터베이스 연동하기
2. 데이터베이스 조작하여 값 추가하기
3. HTML, URL Refactoring
4. 개인정보 수정 기능 구현하기

1. H2 데이터베이스를 사용하여 프로젝트에 데이터베이스 사용!
H2 데이터베이스는 JAVA로 작성된 RDBMS이다. spring-boot jpa 를 이용한다.
JPA에 대한 설명은 다음 포스팅을 참고하길 바란다.

H2 데이터베이스는 별도의 설치가 필요하지 않다. 데이터베이스 실습을 위해 mysql을 설치해본 사람이라면 데이터베이스를 별도의 설치과정 없이 사용할 수 있다는 장점이 얼마나 큰 것인지를 알 수 있을 것이다. H2 데이터베이스는 Maven 라이브러리만 추가해주면 된다. Maven repository 에서 해당하는 라이브러리를 pom.xml에 dependency에 추가해주면 끝이다.

해당 데이터베이스를 GUI 환경에서 보기 위해 콘솔창에 접근할 수 있다.
localhost:8080/h2-console

위 주소로 접근할 수 있고, 로그인 화면에서 jdbc url 에  적당한 url로 connect하면 된다.
잘못 연결하게 될 경우, 객체를 이용해 데이터베이스에 값을 추가해보면 이상한 곳으로 연결되어 있고 현재 프로젝트와는 연결되어 있지 않다는 것을 알 수 있다. H2 데이터베이스와 우리가 작업하고 있는 프로젝트 폴더를 연결시켜야 한다. 이를 해결하려면 스프링 부트 설정 파일에서 어떠한 값(url)을 통해서 연동할 것인지 설정해야 한다. spring boot의 각종 설정은 application.properties 파일에서 설정할 수 있다. 구글링을 통해 어떠한 방식으로 설정을 해주면 되는지 살펴본다. 우리들의 스택오버플로우가 설정 코드를 알려주었다.
1
2
3
4
5
spring.datasource.url=jdbc:h2:~/my-project;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
cs

맨 윗줄 datasource.url 부분이 지금 우리에게 필요한 부분이다. console 창에 입력할 수 있는 부분에 대한 설정들이 그 밑에 나와있다. JDBC URL을 application.properties에 있는 것과 접속을 위한 값을 일치시키자! 드디어 h2-console 창에 접속할 수 있게 되었다. 이렇게 되면 우리 프로젝트와 데이터베이스 연결이 완료된 것이다. jdbc 드라이버를 사용하여 mysql을 연동해봤으면 알겠지만, 정말 너무 간단하다!



2. 데이터베이스 조작하기
위에서 설명했듯이(포스팅 내용 참고), H2는 ORM 방식을 사용하며 객체형 데이터와 관계형 데이터를 맵핑(Mapping)한 것이다. 그래서 우리가 생성한 클래스를 데이터베이스와 맵핑 시키기 위해서 해당 애노테이션을 추가해주어야 한다.

VO객체에 데이터베이스와 연결하기 위한 애노테이션을 추가해준다.
@Entity (import javax.persistence.Entity)

Spring-boot JPA 가  데이터베이스 조작을 위해 제공하는 많은 API들이 존재한다.
Primary key 설정은 @Id 애노테이션을 추가해준다.
Primary key가 natural key일 경우에는 key 값으로 설정하기 위한 인스턴스 변수 위에 애노테이션을 설정해주고,
@Id
private String userId;
synthetic key일 경우에는 새로 field 값을 추가해준다음에 애노테이션을 설정해준다.
후자의 경우 auto increment설정을 해줘야 하기 때문에 @GeneratedValue 애노테이션을 추가해줘야 한다.
@Id
@GeneratedValue
private Long id;

각 필드에 대한 제약조건을 설정하기 위한 애노테이션 @Column 이라는 것이 존재한다. 한 가지 예로 데이터베이스에는 NOT NULL 제약조건이 존재한다. 이 경우에는 @Column(nullable=false) 애노테이션을 설정해준다. nullable default value는 true 이기 때문에 따로 설정을 해주지 않으면 NULL 값이 들어 갈 수 있다. 데이터로 들어오는 값에 대한 제약조건 중 length 값도 Column 애노테이션에서 설정해줄 수 있다. 모든 속성들은 default 값이 존재한다. 애노테이션을 따라 안으로 들어가보면(command 키를 누르고 클릭!) 각종 제약조건에 대한 default 값을 확인할 수 있다.



드디어 VO 객체를 데이터베이스의 한 테이블처럼 사용할 수 있게 되었다.
이제 이 데이터베이스를 조작할 API를 가져와야 데이터베이스를 조작할 수 있겠다! 이를 위해서는 인터페이스를 하나 더 만들어줘야한다. 우리가 생성할 이 인터페이스는 JpaRepository 라는 클래스를 상속받아야 한다. 이 클래스는 Spring-Data JPA에서 제공하는 클래스로 쿼리에 해당하는 내용들이 정의가 되어있다.

아까와 같은 domain package에 UserRepository 라는 interface를 만들어주고, JpaRepository 라는 클래스를 extends 한다. 이 인터페이스는 구현이 필요없다. 자동으로 생성되기 때문이다. 마지막으로 JpaRepository에 Generic을 설정해줘야 하는데, JpaRepository<User, Long>와 같이 해준다. 즉, repository와 연결할 VO객체 명과 primary key 의 타입을 적어주는 것이다.
1
public interface UserRepository extends JpaRepository<User, Long> { }
cs

이제 컨트롤러에서 이 데이터베이스에 접근해보자.
@Autowired
private UserRepository userRepository;
이렇게 repository를 가져오고, repository가 제공하는 api를 통해 데이터베이스에서 데이터를 조작한다.
기존에 arraylist를 통해서 user 객체를 add하고 그 리스트를 model에 넣어줬다면, 이제는 userRepository.save()라는 api를 통해 저장을 하고, userRepository.findAll()로부터 도출되는 값을 모델에 넣어주면 된다. 참고로 findAll() 메소드를 command 키를 통해 들어가보면 값이 List 형식으로 반환되는 것을 알 수 있다. 당연히 반환되는 값들은, 데이터베이스에 존재하는 값들일 것이다. 즉, 템플릿 엔진에서 값을 출력할 때 arraylist를 통해 값을 저장했을 때의 방식과 똑같은 방식으로 값을 출력하면 되는 것이다.



3. html 파일 Refactoring / URL refactoring
HTML 파일 Refactoring
중복을 제거하자! 모든 html파일에서 반복되는 <head> 내용, 매 페이지마다 반복되는 부분을 따로 코드 조각으로 분리시켜 한번에 관리할 수 있게 하자. 공통적인 부분에 약간의 수정사항이 발생하는 경우 모든 html 파일을 돌아디면서 수정할 필요가 없어지는 것이다! html 만으로는 한계가 있기 때문에 우리가 이번 프로젝트에서 사용하는 템플릿 엔진인 mustache가 제공하는 html include를 사용하자! include 폴더에 html 코드 조각들을 모아두고 코드 조각들이 포함되는 부분에 mustache 문법으로 코드 조각을 include해준다.
mustache 문법 상으로 html 코드 조각을 include할 때 root directory가 templates 폴더이므로 {{> /include/header}} 과 같은 형식으로 코드를 추가해주면 된다.

단, 주의할 점이 하나 있다. templates 에 있는 템플릿 엔진인 html 파일들은 반드시 컨트롤러를 통해서 접근해야 한다! 따라서 a태그를 통해 html 파일들을 연결시켜두었다면 각각에 해당하는 @GetMapping 애노테이션을 생성해주고, 전송되는 href의 url 값을 변경해주어야 한다. 그리고 head 태그안에 link 태그로 연결되어 있는 css파일들 또는 Javascript 파일들은 절대경로를 통해서 설정해두자. html 파일 구조가 바뀔수도 있고, url을 이동하면서 depth가 변경될 가능성이 높기 때문이다.


URL Refactoring! 중복 제거, RestFul
@RequestMapping이라는 애노테이션을 통해서 해당 컨트롤러의 Mapping 애노테이션 url의 공통 부분을 따로 빼주는 것이다. 기존에 postMapping과 getMapping에 있던 공통 주소를 빼주고 “” 처럼 빈 url을 넣어준다. url을 추가해줄 경우 @RequestMapping에 설정된 url로부터 시작한다.

RestFul?

post 방식 + user라는 url 이면 user를 post 방식으로 오는 것이니까 새로운 user를 추가하는 것!
get 방식 + user + s 라는 url 일 경우엔 s는 여러 명의 user 리스트를 get 방식으로 불러온다!

거의 일반적으로 get과 post만 사용한다.
원래는 해당 정보를 수정할 때는 put을 사용한다.

그런데 html에는 get과 post만 지원한다.
그래서 약간의 꼼수를 사용한다.
1
2
<input type=“hidden” name=“_method” value=“put”/>
<input type=“hidden” name=“_method” value=“delete”/>
cs
method앞에 언더바를 꼭 붙여야 한다.
method 에 언더바가 있으면 value 값을 토대로 HTTP method를 설정한다.



4. 회원 정보 수정하기 기능 구현
회원 정보를 수정을 하기 위해서는 어떠한 흐름이 필요한지 먼저 살펴보자. 
정보를 수정할 user를 A라고 해보자. 일단 user list 목록에서 정보를 수정할 user A를 클릭하고, 수정할 수 있는 페이지로 이동시켜야 한다. 그리고 이동된 페이지에서는 기존에 설정되있는 user A의 정보를 보여주면 좋겠다. 수정이 완료되면 수정된 값을 새로 데이터베이스에 반영해야 하기 때문에, user A의 id 값과 변경된 정보들을 함께 보내줘야 할 것이다. 이제 코드로 이것을 표현해보자.

user list 에서 정보를 수정할 user를 선택해야 한다. 그러기 위해서는 클라이언트에서 선택한 값에 대한 정보를 서버로 넘겨줘야하는데, url을 통해 넘겨주도록 하자. url로부터 넘어오는 값을 컨트롤러에서 받기 위해서는 애노테이션을 하나 사용해줘야 하는데, 바로 @PathVariable 이라는 애노테이션이다.
1
2
3
4
@GetMapping(“{id}/form”)
public String updateForm(@PathVariable Long id){
     return “/user/updateForm”;
}
cs

이렇게 해주면 컨트롤러가 수정하려고 하는 user의 id 값을 알 수 있는 것이다. 이제는 기존에 설정되어 있던 user의 정보를 수정 페이지에서 보여주자. 일단 넘겨져온 id 값을 통해서 user 값을 데이터베이스에서 꺼내온다. 이때는 userRepository.findOne() 을 사용한다. findOne() 메소드 인자에 클라이언트로부터 넘어온 id 값을 넣어주면 id가 primary key 이기 때문에 해당하는 값을 User 객체로 반환한다. (UserRepository 인터페이스에서 JpaRepository의 Generic으로 설정한 값으로 return 되는 것이다!) 우린 이 값을 model에 넣어 클라이언트에 전달하면 된다.

html 파일에서는 model로부터 해당 값들을 꺼내올 수 있다. 코드상으로는 input tag의 value 라는 속성을 통해서 default 값을 넣을 수 있다.
1
<input type=“text” value = {{userId}} />
cs


1
2
3
4
public String updateForm(@PathVariable Long id,Model model){
    model.addAttribute("user", userRepository.findOne(id));
    return "/user/updateForm";
}
cs

수정을 완료할 때도 마찬가지로 어떤 id에 해당하는 정보가 수정되었는지 컨트롤러에게 함께 보내줘야 한다. 그래야 primary key에 해당하는 id 값으로 데이터베이스에서 조회를 하여 해당 정보의 업데이트를 데이터베이스에 반영할 수 있기 때문이다. 이번에는 컨트롤러에 변경 사항이 존재하는 user 객체가 id 값과 함께 넘겨져 왔다. 우리는 id에 해당하는 user 객체를 데이터베이스로부터 꺼내 새로운 user 객체의 값으로 변경해주면 되겠다. 변경할 때는 User 클래스에 update라는 메소드를 하나 만들어줘서, 새로운 값으로 덮어쓰면 된다. 그리고 그 값을 save 메소드를 통해 데이터베이스에 다시 저장해주면 되는 것이다.

1
2
3
4
5
6
public String updateUser(@PathVariable Long id, User updatedUser){
    User user = userRepository.findOne(id);
    user.update(updatedUser);
    userRepository.save(user);
    return "redirect:/users";
}
cs


1
2
3
4
5
6
public String updateUser(@PathVariable Long id, User updatedUser){
    User user = userRepository.findOne(id);
    user.update(updatedUser);
    userRepository.save(user);
    return "redirect:/users";
}
cs

input tag의 type을 hidden으로 해주어서 id값을 보내줄 수도 있다!
1
<input type=“hidden” name=“id” value=“{{id}}” />
cs

다시 정리 //>
기존에 있는 사용자 정보를 id를 통해 불러온다.
그리고 updateForm으로부터 넘어온 user 정보를 updatedUser라는 인자로 받는다.
기존에 있는 사용자 정보에 set 메소드를 통해 해당 값들을 updatedUser의 변수에 있는 값으로 set해준다.
그리고 이 변동사항을 데이터베이스에도 반영해야 하므로 userRepository의 save 메소드를 이용한다.



#1. 이번 강의 꿀팁 1
ip에 이름 부여하기!
terminal) sudo vi /etc/hosts 로 접근하여, 아래 구문을 추가해준다.
125.209.195.85 my-project
이제는 ip 주소대신 my-project라는 주소로 원격 서버에 접속이 가능해졌다. 접속할 때도 (계정)@my-project 이렇게 접속해주면 되고, 빌드 후 웹 페이지에서 접속해줄 때도 my-project:8080으로 접속해주면 된다.

#2. 이번 강의 꿀팁 2
jar 파일이 아닌 maven 에서 spring boot 프로젝트 실행하기
./mvnw spring-boot:run
./mvnw spring-boot:run &

배포하는 과정까지 반복하면서 프로젝트를 진행하면 좋은 이유
로컬에서 테스트해보는 것과 원격 서버에서 테스트하는 것은 많은 차이가 있다.
로컬에서 테스트할 때는 잘 작동하지만 원격 서버에서는 되지 않는 경우가 많기 때문이다.



반복주기 3. End

항상 좋은 강의 감사합니다!