이전 포스팅은 여기로
개발환경
Os : MacOs 13.4
IDE : intelliJ IDEA Edu
FrameWork : springboot 3.2.2 / jpa / thymeleaf
Launguage java 17
DB : MariaDB 11.2.2
DB tool : DBeaver 23.3.0
FrontEnd Design : Bootstrap 5.3.2
Github : https://github.com/gahyeonkwon/uk_medicine.git
작업 목표
연관관계가 있는 엔티티 RecipeSpec로 데이터 삽입하기
@Setter, @AllConstructorm @NoArgsConstructor에 대한 고찰
Oracle Cloud 를 사용하여 배포하기
- 연관관계가 있는 엔티티 RecipeSpec로 데이터 삽입하기
RecipSpec 같은 경우 아래와 같이 엔티티를 정의했다.
@Entity
@Table(name = "medi_recipe_spec")
@Getter @Setter
@Slf4j
@ToString
@NoArgsConstructor
public class RecipeSpec {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "spec_id")
private Long id;
private Double materialMount;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "recipe_id")
private Recipe recipe;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "material_id")
private Material material;
@Builder
public RecipeSpec(Long recipeId) {
this.recipe = Recipe.builder().id(recipeId).build();
}
}
이때 Recipe 별로 들어가는 Material Mount의 양이 다르므로 Recipe와 RecipeSpec 사이에는 1 : N, Material 과 RecipeSpec 사이에는 1:N 가 발생했다. 따라서 @ManyToOne 관계를 지정했고, Recipe 와 Material의 데이터가 삭제될 경우 RecipeSpec 도 자동으로 삭제 될 수 있도록 CascadType.ALL ( persist + remove 기능을 한 번에 지정하기 위해서 )을 지정했다.
프런트에서 서버로 받아올 때부터 List <RecipeSpec> 형태로 가져오고 싶었는데 ( 엔티티 자체를 리스트처럼 ) 그런 기능은 아무리 찾아도 안 보여서 materialMount ( 재료별 용량 )과 materialId 만 list로 가져오고 서버단에서 List <RecipeSpec>을 생성한 뒤 saveAll 처리를 하는 방식으로 구현했다.
작업할 때 가장 많이 마주쳤던 에러는 다음과 같다.
- RecipeSpec save 시 발생 - 에러 1
org.hibernate.PersistentObjectException: detached entity passed to persist: medicine.db.item.RecipeSpec
- RecipeSpec save 시 발생 - 에러 2
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: medicine.db.item.RecipeSpec
- 해결방안
검색하면 엔티티 종속성 문제가 있을 수 있으니 CascadeType.All을 지우라는 내용이 많이 나와서 지워 봤지만 변하는 건 없었다.
두 번째 가능성은 객체 생성에 문제가 있을 수 있다는 내용이 많아서 코드를 다시 보니 RecipeSpecId 값에 RecipeId를 넣어서 생성하고 있었다. RecipeSpecId의 경우 자동 생성으로 설정해 놨기 때문에 문제가 발생한 거였다.
Recipe 객체를 정상적으로 생성하도록 수정하고 나서는 문제없이 save 가 되는 걸 확인했다.
- @Setter, @AllConstructor, @NoArgsConstructor에 대한 고찰
DTO 뿐만 아니라 Entity를 사용하게 되다 보니 늘 사용하는 @Setter, @AllConstructor, @NoArgsConstructor에 대해 고민이 생겼다. 이전글에서 builder 패턴사용 시에 @AllConstructor 이 강제됨에 따라 접근범위에 대해 어떻게 설정할지, 명시적 생성자에만 사용할 것인지 전체 클래스에 대해서 설정할 것인지.. 고민했었는데 여러 가지 글을 읽어보고 나름대로 정리한 결론은 다음과 같다.
- dto에서는 크게 상관없는데 entity에서는 setter를 지양하자
- private로 선언한 생성자에 @Builder 패턴을 추가하여 사용하자 ( 권장사항이며 , 명시적 생성자에 해당한다 )
- @NoArgsConstructor를 아예 사용하지 않을 수는 없으므로 AccessLevel을 Protected로 설정하여 외부에서 무분별하게 사용되는 것을 방지하자
- Oracle Cloud를 사용하여 배포하기
사이트는 다 만들었으니 이제 친구에게 만들었다고 보여줘야 하는데, 로컬에다 배포해 주고 쓰라고 할 수는 없으니까 외부에서도 편하게 사용할 수 있도록 클라우드서버를 찾았다. AWS는 예전에 회사 데모 사이트 올린다고 개인 카드 등록했다가 사용하지도 않은 것 같은데 뭐가 잘못 됐는지 돈이 나갔던 슬픈 기억이 있어서 배제하고 무료로 사용가능한 Oracle Cloud를 선택했다.
그런데... 무료라서 그런지 겁나 느리다. yum으로 파일 받다가 느린 게 아니라 서버가 멈춘 줄 알고 두세 번 정도 껐다 켰음 ㅎ
아무튼 사용 방법은 다음과 같다.
- 회원가입을 하고 카드 등록을 한다
해당 사이트에 들어가서 단계별로 회원가입을 하고 나면 카드 등록을 하는데, 사이트 상태가 좋지는 않다. 나는 한 시간 정도 가입하려고 삽질을 하다가 페이지가 만료 됐다고 오류가 나서 포기하고 하루정도 있다가 다시 했는데 한 번에 됐다. ( 브라우저도 바꿔보고 시크릿모드도 해보고 했지만 바뀌는 건 없었다 ㅠ )
- 로그인 후 Launch Resources > Create a VM instance 선택하여 instatnce 생성
생성 시 옵션은 대부분 그냥 무료로 주는 걸 선택했다.
- secret / pub key download
Instance를 생성하고 나면 secret / pub key 다운로드하는 항목이 있는데 둘 다 다운로드하여 준 뒤 Local에서 home 디렉터리의 ssh 폴더 하위에 이동시킨 뒤 할당 된 Public IP로 접속하면 된다.
ssh opc@[public IP]
나는 원격툴을 안 쓰고 터미널에서 바로 시도했는데 초기 접속 시 아래와 같은 오류가 발생했다.
Permission denied publickey, gssapi-keyex, gssapi-with-mic
공홈에 연결하는 내용도 잘 나와 있는데 내가 정독을 안 해서 발생한 문제였다. key에 400 권한을 주라길래 권한을 할당하고 아래와 같이 접속하니 정상적으로 됐다.
chmod 400 [privatekey file]
ssh -i [key_name].key opc@152.67.196.50
https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/connect-to-linux-instance.htm#top
- yum으로 필요한 파일 설치
서버에 접속이 정상적으로 됐다면 git을 설치해주자. opc 계정이 관리자 권한을 사용할 수 있기 때문에 sudo 명령어를 사용하면 된다.
sudo yum install git
입력하니까 또 이런 오류가 떴다.
Failed loading plugin "osmsplugin": No module named 'librepo
sudo dnf install python3-librepo -y
를 사용해서 해결했다.
참고로 설치하는데 생각보다 엄청 오래 걸린다. 서버가 멈춘 게 아니니까 화면에 반응이 없어도 기다려보는 것을 추천한다.
git을 설치한 뒤 mariadb와 java 17 도 설치해 줬다. mariadb를 서비스로 시작하려면 systemctl을 사용해야 하는데 이때 mariadb-server 가 필요해서 함께 설치했다.
sudo yum install git
sudo yum install mariadb
sudo yum install mariadb-server
sudo systemctl start mariadb
sudo yum install java-17-openjdk-devel.x86_64
db 서비스가 정상적으로 뜬 것을 확인하기 위해 port LISTEN 정보를 확인했다.
netstat -nao | grep 3306
- db 마이그레이션
dbeaver에서 데이터베이스를 선택하고 우클릭하면 mysqldump를 사용할 수 있다. ddl과 dml을 뽑아내서 저장한 뒤 git에 올렸다.
git clone을 실행하면 서버에 해당 파일이 올라오는데 mysql 서버에 접속해서 database를 만들어 주고 생성한 dump 파일을 실행해 줬다.
create database uk_medicine;
use uk_medicine;
source /home/opc/uk_medicine/db.sql
grant all privileges on uk_medicine.* to root@'localhost';
flush privileges;
- git clone을 통해 배포하기
/home/opc 하위에서 git clone 을 통해 프로젝트 폴더를 다운로드한 뒤 빌드를 실행했다.
git clone https://github.com/gahyeonkwon/uk_medicine.git
chmod +x gradlew
./graldew build
빌드 도중 다음과 같이 오류가 발생했다.
java.lang.ClassNotFoundException: org.gradle.wrapper.GradleWrapperMain
테스트 코드 작성한 게 있었는데 서비스 로직을 변경하면서 남아있던 더미 테스트 코드가 빌드 시에 문제가 된 거라서 해당 코드를 주석처리 하고 재빌드하니 정상적으로 되는 것을 확인했다.
cd /home/opc/uk_medicine/build/libs 에 jar 파일이 정상적으로 뜬 것을 확인한 뒤 백그라운드로 실행시키기 위해 nohup java –jar [빌드된 jar 파일] & 로 실행 시켰다.
- 외부에서 접속하기
ps로 프로세스가 뜬 것을 확인하고 내부에서 curl까지 호출해서 페이지가 정상적으로 뜨는 걸 봤는데 외부에서 접속이 안 됐다. 서버로 접근 로그가 찍히지 않아서 방화벽을 확인해 보니 방화이 문제였다. 아래와 같이 추가하니 정상접속 됐다.
sudo iptables -I INPUT 1 -p tcp --dport 8080 -j ACCEPT
- 그 외 오류
접속은 잘 됐는데 페이지 이동시 또 오류가 났다. 오류 좀 그만 발생해 주세요...
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [/recipe/selectMaterial], template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:869) ~[thymeleaf-3.1.2.RELEASE.jar!/:3.1.2.RELEASE]
이게 개발이랑 로컬에서는 안 나던 오류가 운영에서만 났는데, view 경로 매핑할 때 '/'를 제거하니까 정상적으로 됐다.
예를 들어 Controller에서 /home/test로 되어 있다면 home/test 로 변경해 줬다.
이로서 클라우드 서버에 성공적으로 배포했다. 하면서 자동배포의 필요성을 다시 한번 느꼈다.
그래서 다음 포스팅은 Spring Cloud를 사용해서 CI/CD부터 젠킨스사용하는 것까지 정리해보려고 한다 ^0^ 목표는 다다음주 이내로 정리하는 거다. 커밍 쑨 !
'Dev > Toy Project' 카테고리의 다른 글
개인프로젝트_Ukmedicine_3 (1) | 2024.02.25 |
---|---|
개인프로젝트_UKmedicine_2 (1) | 2024.02.20 |
개인프로젝트_UKmedicine_1 (0) | 2023.12.11 |