설정 잘못했다가 경로 인식 못해서 회사에서 너무 민망했는데 (아무리 초보들꺼여도 우리플젝 욕먹음 당연 속상함) 이게 설정을 블로그 짜깁기로 보다보니 제대로 꼬여서 그런거였다. 스프링부트 버전이 높아서 인식이 안되나 별별 생각을 다 했는데 집에 와서 찬찬히 풀어보니까 되네 돼!!!!!!
1. 일단 jsp를 설정하며 마주친 에러.
jstl에 대한 dependency를 꽂았는데 나는 분명 처음에 에러가 안났다. 그래서 여기에 문제가 있는줄 몰랐는데 pom파일부터 차근차근 다시 저장하면서 갑자기 에러가 생겼다..? 문제가 있음 빨리빨리 말해라...!!!!!
Multiple markers at this line - Project build error: 'dependencies.dependency.version' for javax.servlet:jstl:jar is missing. - 'dependencies.dependency.version' for javax.servlet:jstl:jar is missing.
starter 는 중복이라 주석처리함
👉 에러창을 읽어보면 jstl:jar missing 그리고 version이 missing이라고 나온다.
각종 블로그글에는 embed-jasper와 jstl 세팅만 있고 jstl의 버전설정이 빠져있다. gpt도 마찬가지. 그냥 버전을 추가해주면 된다...하.... 🤦♀️🤦♀️🤦♀️
2. 타임리프가 경로를 못찾아요
application 세팅에서 jsp와 타임리프는 각각 따로 설정을 해주어야 한다. 그래서 yml서식에 맞게 잘! 들여쓰기로! 해줬는데! 자꾸 아래처럼 타임리프를 못찾는다고 나오는것....환장..... templates 폴더 하위에 thymeleaf폴더 잘 만들어줬는데 왜 못찾니 한참을 고민함.
Error resolving template [thymeleaf/index], template might not exist or might not be accessible by any of the configured Template Resolvers
회사에서 이거만 한시간을 검색하고 고민했을것임. 폴더를 통채로 붙여넣었어도 노트북이 새로 바뀌면서 설정이 뭔가 바꼈을수 있다...는 말씀을 듣고 그런가...싶어서 이거 할 마음이 뚝 사라졌었음.
그리고 또다시 여러 글을 읽어보는데, 다른분의 설정파일에서 나랑 다른걸 발견했다. 하..설마..? 하고 추가했는데 맞았음.
👉 나는 prefix에 /templates/만 써놓았어서 분명 해당폴더에 다 들어가있는데 한참 찾았더니 classpath:가 추가되어야 했다.
그래서 결국 인덱스 화면 잘 뜹니다 ^^...
함정은 나는 관리자화면에서 연습하려고 했더니 거기까지 로그인하고 뭐하고 하는 여정이 너무 길다. 왜냐. 이제 디폴트가 jsp로 변경되었기때문에 (return "index"는 jsp를 찾아감) 기존의 타임리프 화면은 retun에다가 모두 타임리프를 붙여줘야 하기 때문이죠... (뷰 네임에 그렇게 세팅되어있으므로)
이렇게 말입니다
이 많은 화면에 다 붙이는건 쓸데없는 노가다이므로 그냥 로그인없이 접근하는 화면 하나 만들어서 작업해야겠다.
으휴 ㅠㅠ 개똥멍청이 될뻔했는데 그래도 멍청이정도는 되겠지 이제. 못살아..............
본격적으로 오류를 수정해보려던 찰나... 분명 url에서 localhost로만 바꿔주면 된다는 말을 들었는데 ... 라고 생각해서 password를 봤더니 mariadb만 다르게 설정되어있었다 ^^... 패스워드 바꿔주니까 잘 접속됐음. (이와중에 localhost:3306/DB이름? <-여기 매칭 안시켜줘서 자꾸 못찾는다고 함. 대소문자도 구별하더라...)
boot의 application-dev.yml파일에서 서버 접속을 설정할땐 url: jdbc:mariadb://localhost:3306/octapdb?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 이렇게 localhost로 두고, 도커에서 3306:3306 으로 컨테이너를 생성했으므로 내가 3306으로 접속 ▶ 해당 컨테이너 ▶ octapdb로 이동이 다이렉트로 이루어진다. 너무 신기했음. 당연히 컨테이너는 running 이어야 한다!
하지만 localhost:8081로 접속하면 아래와 같이 에러메세지가 뜨면서 프로젝트에 세팅해놨던 '접속불가페이지라 홈화면으로 이동합니다' 화면이 무한루프로 돌았다... 이때부터 권한문제라는 생각이 들었다. (내가 만들었던 화면인데 정말 얄미웠음.....)
SpringBoot 콘솔창에 뜨는 에러문구
일반 접속자(비회원)면 기본 인덱스 화면으로 들어갈 수 있게 해뒀는데 왜 권한에러가 뜨나...싶어서 에러문구를 살펴보는데 definer/invoker of view lack rights to use them이 문구가 눈에 들어왔다.
뒤져보니 mysql은 view는 table과 다르게 또 권한 (조회할 유저 설정)설정이 필요한것 같았고, 인덱스 화면에서 해당 뷰 데이터에 접속하려는데 권한이 없음 → 데이터 접근 불가한 유저라 에러화면에서 무한루프로 돌고있다고 추측했다.
원래 DB를 확인해보니 view 쿼리문에 definer가 기본으로 들어가있었다
이걸 보고 view를 열어봤더니 세상에 Definer가 왜 저모양이죠..? 나는 분명 데이터를 그대로 import했는데.....
왜 섞여있니..?
나는 root ID로접속했으므로 중간중간 root@%로 설정된 view를 열어보면 데이터가 잘 보이지만, octap@%로 된 뷰들은 접속하면, (속성은 그대로 잘 들고왔는데) Data가 아래와 같이 invalid table or column or function or definer/invoker of lack rights to use them. 으로 나온다.... 그렇담 뭐다? Definer를 수정해줘야 할 것 같다!
뷰에서 데이터를 볼 수가없으니 화면에도 뿌릴수가 없다...
그리고 방법을 알려주는 티스토리를 발견. 뒤져보니 ALTER를 통해서 해당 내용을 수정할 수는 있는데, 문제는 as 뒤에 뷰 생성 쿼리를 그대로 다 붙여줘야 한다는 것... 이게 무슨 노가다인지...ㅠㅠ 말이 alter지 그냥 새로 만드는거나 다름없었다. 한번에 바꿀수 없나 싶었는데 이러나저러나 쿼리문이 붙는걸 보고 걍..그냥..다...수기로 돌렸다.....
플젝할땐 내꺼하기 바빠서 다른사람 코드를 깊이 들여다보질 못했는데 (심지어 난 갖다쓸일도 없었음) 복기한다고 열어보다보니 흥미로운 부분들이 보인다.
<select id="similarPersonalityFindAll" resultType="MissionView">
select * from
mission_view
where
end_date>=curdate()
and personality_id = 1
order by
rand()
limit 4
</select>
이 부분은 인덱스 화면에서 비회원을 대상으로 보여주기위해, 쿼리문을 세팅해둔 부분인데, 다른건 다 알겠지만 order by 구문이 이해가 안갔다.
컨트롤러에서 어떻게 처리해야하나...고민하며 검색하던 중에, css에서 줄바꿈 처리 속성인 white-space를 가지고 적용하면 된다는 이야기를 우연히 보게됐고 , 아니 이게 줄바꿈 처리하는 속성은 맞긴 맞는데 저장할때부터 잘만 저장해주면 이거 하나로 해결되는게 정말일까? 싶어서 헐레벌떡 해봤다.
▼ 처음봤을땐 이해못해서 지나쳤다가 (왜냐면 css만 한번 적용했었는데 안되길래 걍 안되는줄 알았음) wrap:hard 로 속성 저장 가능한걸 보고 다시보니 그걸 그대로 꺼내면되는구나!!!! 하고 이해한 글
😂 역시나 수업시간 외에 처음으로 해본 방식이라 다사다난했다. 마지막즈음 가니 어떻게 서로 연결시키는지 조금 감이 와서 페이저 구현이 오히려 쉬웠다!
😂 동시에 느낀 것. 이래서 브랜치가 필요하구나... 그냥 내 브랜치에서 파일만 복사해서 작업하려다가 혹시 모르니까 브랜치 하나 더 파서 거기서만 작업했다. 버전관리라는게 이런느낌이구만.
1. 이 문구는 무슨뜻인가
You are running a development build of Vue. Make sure to use the production build (*.prod.js) when deploying for production.
이 메시지는 Vue.js 애플리케이션을 개발 환경에서 실행할 때 나타날 수 있는 경고 메시지입니다. Vue.js는 개발 버전과 프로덕션(배포) 버전으로 나누어집니다. 개발 버전은 개발 중 디버깅 및 개발 툴의 지원을 위해 추가 로깅 및 경고를 포함하고 있습니다. 프로덕션 버전은 더 최적화되고 경량화되어 성능이 더 좋으며, 애플리케이션 배포에 사용됩니다.
해당 메시지는 개발 환경에서 Vue.js 개발 버전을 사용하고 있을 때 나타나며, 실제로 애플리케이션을 프로덕션 환경에 배포할 때는 Vue.js 프로덕션 버전을 사용하는 것을 권장합니다. 프로덕션 버전은 추가적인 디버깅 정보와 경고를 제거하여 애플리케이션을 더 가볍게 만듭니다.
프로덕션 버전의 Vue.js는 .prod.js 확장자를 가진 파일로 제공됩니다. 이 파일을 사용하면 위의 경고 메시지를 제거하고 애플리케이션을 프로덕션 환경에 배포할 수 있습니다.
따라서 개발 중에는 개발 버전을 사용하고, 애플리케이션을 배포할 때는 Vue.js 프로덕션 버전을 사용하는 것이 좋습니다.
👉 수업시간엔 왜 눈치 못챘는지 모르겠는데, 이제서야 눈에 들어와서 찾아보니 개발용과 배포용을 안내해주는 문구였음.
2. id를 못찾아서 mount가 안된다면 잊지말고 defer 처리하기
👉 이 부분을 까먹고있었어서 id에 오타가 있나 싶었는데 파일을 읽을수 있도록 처리를 안해줘서 그런것.........으이그
3. 원하는 형태로 날짜 포맷하기 (compute와 메서드 사용)
1. 필터사용
filters:에 포맷하고 싶은 형태를 정의해서 쓸 수 있다는데, 나는 자꾸 global에 필터명이 등록되지 않았다는 알림이 나왔다. 일단 패스.
2. 다른사람들이 쓴다는 라이브러리 (dayJs)
vue도 처음 써보는 마당에 굳이 다른 라이브러리로 고민하고 싶지 않아서 패스. 그리고 필터 사용에 관한 포스팅 쓰신분이, 어렵지도 않은거 그냥 필터 만들어 쓰신다기에, 나도 기존에 써둔 날짜포맷 코드를 살리기로 했다.
자바에서는 알아서 판단이돼서 (?) 잘 썼는데 여기는 정신이 없어서 자꾸 놓치고 있다. 자스 많이 안해본 티가 여기서 나나요.
data() {
return {
fixed: [],
list: [],
pager:[],
}
},
computed: {
//생략
},
},
created() {
console.log("생성됨")
this.loadFixed();
this.loadList();
this.countList(this.pager); //여기에 pager라고만 썼다가 왜 연동이 안되지(?)함
},
methods: {
//다른 메서드 생략
async countList() {
let response = await fetch(`/api/notices`)
let listCount = await response.json();
console.log(listCount);
let page = 0;
if (listCount % 5 > 0)
page = parseInt(listCount / 5) + 1
else
page = parseInt(listCount / 5)
//마지막 페이지 업데이트
this.lastPage = page;
//pager[] 업데이트
for (let i = 0; i < page; i++) {
this.pager[i] = i + 1;
/*this.pager.push(i+1);*/
}
},
2. Vue.set대신에 this.pager에 대입하면 안되나?
this.pager[i] = i + 1과 Vue.set(this.pager, i, i + 1)는 데이터에 새로운 속성을 추가하는 데 사용됩니다. Vue.js 내부적으로 데이터의 변경을 감지하기 위해 리액티브 시스템을 사용하고 있으며, 데이터 변경을 추적하고 화면을 다시 렌더링하는 데 필요한 업데이트를 수행합니다.
this.pager[i] = i + 1는 일반적인 JavaScript 코드로, Vue의 리액티브 시스템에 대한 인식이 없기 때문에 Vue가 이 변경을 추적하지 못할 수 있습니다. 따라서 새로운 속성을 추가할 때는 Vue.set을 사용하여 Vue에게 이 변경 사실을 알려야 합니다.
따라서 데이터 변경을 Vue에게 알리기 위해 Vue.set 또는 this.$set을 사용하는 것이 좋습니다. 이렇게 하면 Vue가 변경 사항을 정확하게 추적하고 컴포넌트를 업데이트할 수 있습니다.
vue.set을 쓰지않고 for문으로만 배열 업데이트를 했을때, 화면에서 2까지 입력된건 반영이 되어서 페이저 디자인은 2번 반복 되었다! 다만 안에 있는 숫자 1,2가 출력이 안되는 상황. 위에 있는 data:{pager:[]}에 반영이 안되고 있는것 같았음. 함수 내에서 pager자체에 1,2가 들어간건 맞는데 말이야 (콘솔내용처럼)
도대체 this.$set을 어떻게 꾸려야 할까 엄청 고민했는데 이것저것 하다보니 원래 했던 this.pager[i]=i+1도 되는게 맞고 this.pager.push(i+1)로 collection을 활용하는것도 맞았다. 결국 잘 출력됨 ^^;;;
문제는 내가 출력하는 방법에 있었다.
<li v-for="(p,index) in pager"><a class="pager selected" href="">{{p}}</a></li>
반복문으로 pager를 뽑아서 뽑아온 키 p의 값에 접근하므로 {{p}} 자체가 값으로 바인딩 되는건데, 순간 착각해서 {{p.index}}로 써놓은게 함정이었다. index는 js에서 활용할 필요가 있을때 뽑아쓰려고 설정한다 생각해야함. (각 클릭값을 눌러서 페이지를 변경해야하기때문에 결과적으로 있긴 있어야 한다)
3. 클릭이벤트와 메서드 연동하기
👉 이쯤되니까 필요할때마다 data에 뭘 선언하고 어떻게 뷰와 연결하는지 좀 더 감이 왔다. 타임리프처럼 it문이나 classappend안쓰고 선택된 페이저의 css class를 어떻게 끼우나 했는데, 돌아가는게 보이기 시작하니까 인덱스 맞추기가 쉬워졌음! 며칠 뒤에도 기억할 수 있을지 의문이라 기록해두긴 하지만 ^^..
결과물!
👉 타임리프 쫙쫙 빼고 html을 최대한 뼈대로 남겨둘 수 있어서 좋았다. 다만 화면이 번쩍거리는게 SSR방식에 비해서 너무 신경쓰인다.
현재까지 구현기능
1. 중요공지 3개까지 상단고정됨 (관리자페이지 버튼과 연동)
2. 즉, 페이저 버튼 누를때마다 일반공지 리스트만 변경됨
3. 세모 아이콘은 맨앞, 맨뒷페이지로 이동하고, 숫자는 게시글 개수에 맞춰서 생성됨. (현재 일반공지 글 8개라 2페이지까지 생성)