https://velog.io/@aidenshin/Optional-%EA%B4%80%EB%A0%A8..
https://velog.io/@aidenshin\
https://www.notion.so/Optional-05043cddaf7d47548a44f3d7677c1d00
runtimeException은 캐치 안해도 실행 가능. 대신 핸들러로 처리해주는 것이 좋다?????
DB의 가장 중요한 특성은 구조화된 데이터이다.
여러 정보가 있지만 산발적으로 흩어져있으면 DB가 아니다.
데이터를 구조화하면 다음과 같은 장점이 있다.
데이터를 정렬할 수 있다.
정보의 종류에 따라 검색할 수 있다. 바다 중 앨범에 해당하는 것만 검색가능하다. 곡명 바다 배제
정보의 성격을 명확히 빠르게 파악할수있다.
방대한 데이터를 체계화해 보관할수있다.
데이터베이스 서버에 접속할때의 클라이언트.
웹브라우저를 이용해 웹서버에 접속하는데, 웹서버에는 크롬 등이있다.
마찬가지로 DB에 접속하는 것이 DB클라이언트이다.
엑셀과 마찬가지로 열에는 데이터의 종류가 구분되고 각각의 행에는 데이터 셋이 저장되어있다. 행별로 하나의 그룹이다. DB는 쿼리를 이용해 여러 작업을 할수있다는 점이 엑셀과 다ㅏ르다.
쿼리를 이요해 데이터 추가 삭제 변경 조회하는 작업을 할 수 있다.
https://www.youtube.com/watch?v=-J9f_LQILCY
로그인 폼은 static에 있고 중복이 많다. 중복제거하기 위해선 html에선 불가능하기에 template 쪽으로 와야한다. 그러려면 접근할수있는 컨트롤러를 만들어야한다. UserController을 사용하자.
@GetMapping("/users/loginForm") return"/user/login"해서 static의 login.html을 템플릿으로 이동시키며 된다. (MVCConfig에서 일괄 매핑해줬던 걸로 기억 -ㅅㅈ)
정
ctrl shift w 전부 닫기
html은 타입이 아니라 리소스이기 때문에 ctrl shift r 로 열 수 있다.
유저컨트롤러에서 로그인 기능 구현
@PostMapping(/login)
패스워드 등이 넘어오기때문에 포스트로 하는 것이 맞다.
String longin(String userid, String password)
return redirect:/ 해서 메인페이지로 이동하도록 한다.
그러면 userId에 해당하는 회원이 db에 존재해야한다.
User user = userRepositor.findOne(userId);로 조회한다.
user의 기본 키는 userId 가 아니라 Long id이다. 그러면 Long 타입 id로 사용자 조회할수있는데, 우리는 이것을 모르고 userId만 알고있다. 그러면 우리는 userId를 기반으로 조회를 할수있다. 지금은 long타입 아이디를 전달해야하는데, userId기반 조회할수있도록 UserRepository에
User findByUSerId(String userId); 를 추가해준다. 얘는 userId를 기반으로 user를 찾을수있다.
if(user==null) {return redirect:/users/loginForm} 아이디에 맞는 사용자 없으니까 로그인 폼으로 다시 돌아가라
if(! password.equals(user.getPassword)) { return redirect: users/loginForm}
로그인 되면 로그인됐다는 정보를 어딘가에 저장해야한다. 쿠키와 세션 중에 세션을 사용해서 로긘 사용자정보를저장해보겠다. 그러려면 모델에 저장했던 것처럼 HttpSession을 통해서 저장할수있다.
session.setAttribute("user", user);
이렇게 하면 로그인 안됐을때는 로그인페이지로 이동하고, 로그인 잘 되면 세션에 로그인사용자정보를 저장한 후에 메인페이지로 리다이렉트하는 방식으로 끝낼수있다.
서버에 데이터 잘 오는지 확인하려면 코드 사이사이에 프린트, 로거 등을 통해 login success 등의 깃발꽂기 해도 된다. 현재 프로그램 상태 확인위해 로그드 보면 어디서 문제생겼는지 서버상확인할 수 있다. 이것을 디버그 로그라고 한다. 로깅라이브러리를 통해 문제점을 개선할수있다.
https://www.youtube.com/watch?v=9xmTAmyv_ic
로그인상태에 따라 메뉴처리하고 로그아웃하는 방법을 살피겠다. 로그인 처리를 한다는 거승ㄴ, 메뉴상에서 4개의 메뉴가 항상 나타나야하는것은 아니다. 로그인과 회원가입은 로그아웃상태에서 나타나야하고, 로그아웃과 개인정보수정은 로그인상태에서 나타나야한다.
navigation.html에서 if else가 필요할 것이다.
로그인을 위해서 우리가 매번 회원가입을 해ㅑㅇ하는 귀찮은 작업이 있는데, test data를 db에 넣을 방법을 고민해야한다. 단순반복작업을 효과적으로 개선할 방법을 찾아야한다. 스프링 부트를 보면 spring jpa data initialize database 검색하면 된다.
import.sql을 클래스패스 루트에 두면 된다고한든데, src/main/java가 클래스패스 루트가 될수도있고, resources도 클래스패스의 루트가 된다. main java는 자바 소스코드를 관리하는 곳이기때문에, resources에서 import sql을 관리하는 것이 좋겠다.
file명을 만드는데 이름이 import.sql 이고,
파일 안에는insert 문을 이용해서 INSERT INTO USER(USER_ID, PASSWORD, NAME, EMAIL) VALUES ('javajigi', 'test', '재성', 'javajigi@slipp.net')
id는 자동증가하기 때문에 빠진다.
로그인 상태에서 mustache 문법에서 템플릿 엔진쪽에서 쿼리를 해야하는데 로그인이 될때 찾아볼수있따.
session에 user이란 이름으로 user전달했는데 이게 템플릿엔진에 잘 전달되는지 잘 모르겠다. 이걸 알려면 if else는 머스태시 템플릿에서 없다. 그렇기 때문에 if를 두번쓰자.
{{#user}}{{/user}}user가 존재할때
{{^user}}{{/user}}user가 존재하지 않을때
세션에 담긴 데이터는 머스태시에서 어덯게 갖고 오나? mustache spring session검색
스프링부트 설정중에 세션데이터ㅏ를 모델에 추가해주는 설정이 있다. 디폴트는 폴스인데,
application propertie에다가 spring.mustache.expose-session-attribute=true를 추가하면 세션에 담긴 데이터를 모델에 담아 템플릿엔진에 전달해주는 것으로 보인다.
로그아웃기능
href='users/logout'
getmapping (/logout) return redirect:/ 메인으로 이동.
세션에 데이터를 저장했으니까, 한번 저장되면 웹페이지를 이동해도 계속 저장된 상태로 유지된다. 로그아웃할떄는 세션에 담긴 유저데이터를 제거해주면 된다. sesssion.removeAttribute("user")l;
세션을 이용해서 로그인상태정보를 계속 유지할수있따. 어떻게동작하는지는 쿠키 세션 두가지 키워드로 검색하면 많은 문서있으니참고해라.
다음 시간에는 로그인을 했을 때 로그인된 사용자에 대해서만 개인정보수정할수있게하겠다.
https://www.youtube.com/watch?v=HfW5kvsaAEA
/users로 접근하면 회원목록 접근가능했다. 자기자신의 정보만 수정할수있도록 개선하겠다.
개인정보수정을 클릭하면 어떤 사용자인지 알수있어야한다. 그래야 url을 바꿀수있다.
navigation에 개인정보수정버튼이 user로 접근하는 것을 href '/users/{{id}}/form'으로 접근을 바꾼다.
세션 데이터를 담을때 expose하면서 데이터 담았는데 user이 둘다 user이기 때문에 전달이 안된다. 그렇기 때문에 세션에 담긴 유저를 sessionedUser로 바꿔야한다. 네비게이션 html에서도 (로그인 전후) sessionedUser로 수정해준다
개인정보수정시 모델에 담긴 데이터와 세션에 담긴 데이터의 이름을 다르게 변경해줘야 에러가 없다.
url을 통해 직접 접근하면 로그인하지 않아도 접근할수있는데, 이것은 문제가 있다. 그렇기 때문에 개인정보수정하면서 이동할때 httpsession으로 사용자가 로그인했는지 안했는지 알아와야한다.
User sessionedUser = session.getAttribute("sessionedUser);
세션에서 값을 꺼내면 오브젝트로 나와서 에러가 난다. Object로 받은 다음에
if(sessionedUser ==null) {
return redirect:/users/loginForm 로그인페이지로 이동하도록 ...
}
이렇게해야 보안 문제가 없다.
새로운사용자가 있다고 하면, 로그인만 돼있으면 다른 사람의 users/1/form로 접근가능하다.
userId @Column 에 unique=true하면 userId똑같은 값 안됨
로그인사용자인경우만 체크했기 떄문에 다른 사용자의 데이터 확인가능하고 수정됟되기때문에
client에서 전달되는 id에 대핟ㅇ하는 사용자를 조회하고있는데, 이렇게 하면 안되고 로그인된 사용자의 정보를 db에서 가져오도록 설정ㅇ을 해야한다. 여러 방법 있지만 sessionedUser이 null이 아닐 경우 user로 캐스팅해야한다.
업데이트 부분도
로그인된 사용자여야하고, ㄱ 중에서도 수정하려고하는 id와로그인 사용자가 같을 경우에만 해당 정보를 업데이트 된 사용자 정보로 수정할수있다.
로그인성공을하면 세션정보를 통해 로그인된 사용자가 누군지 식별 가능하기 때문에 이 작업을 할수있는지 없는지 판단하고 그에 대한 방어코드를 만드는 연습을해야한다.
https://www.youtube.com/watch?v=DaqWKDvdmAk
세션관련 부분에서 많은 중복이생겼다. 이 사용자가 로그인된 사용자인지, 로그인사용자를 얻어오고 캐스팅하는 부분 등이 중복이다. 하드코딩된 부분을 보면
세션을 담당하는 별도의 유틸클래스를 만들겠다. httpSessionUtils클래스를 만들어 중복제거해보겠다.
하드코딩되어있는 부분을 뽑겠다.
public static final String USER_SESSION_KEY="sessionedUser";
머스태ㅣ에 헬퍼 클래스를 만들지 않는이상 html에서도 중복제거하기는 어렵다.
public static boolean isLoginUser(HttpSessionsession){
Object sessenedUser = session.getAttriute(USER_SESSION_KEY)
if(sessionedUser==null){
return false;
}
return true; // null 아니면 true
}
public static User getUserFromSession(HttpSession session) {
if(!isLoginUser(session)){
return null; //null이 좋은 습관 아닌데 로그인안했다면 로그인유저는null일것
}
return (User)session.getAttribute(USER_SESSION_KEY);
}
메서드명이 길어지더라도 의도를 명확히 전달하는 것이 좋은 습관이다.
객체는 user인데,
개발자들 개발과정에서 상태정보를 갖고있는 user에서 데이터를 꺼내서 비교하는(equals) 코드가 많다. 그런데 데이터 가진 객체에서 데이터를 꺼내는것보다는 데이터를 가진 객체한테 일을 시키려고해얗나다. user한테 비밀번호가 같은지 메세지를 보내는 것이다. 즉 User클래스 안에 matchPassword(string password){
if(newPassword == null){
return false;
}
return newPassword.equals(password);
}
-> 이것을 사용해서
user.matchPassword(password)
와 같이 user패스워드가 일치하는지 메세지를 보낸다. user의 패스워드 데이터를 겟으로 꺼내는 것이 아니라메세지를 보내는 것이다. 이렇게 되면 getpassword와 같은 메소드가 필요없어진다.
getId도 마찬가지로
boolean matchId(Long newId) {
if(newId==null){
return false;
}
return newId.equals(id);
}
get을 통해 상태 데이터를 외부에 노출시키는 것이 좋지 않다. 데이터를 가진 객체에게 많은 일을 시켜야한다. 객체지향 개발에서 1단계로 이부분부터 의식적으로 연습해야한다.
데이터베이스 쿼리를 확인할수있다.
사용자목록 조회할 때 디비에서 가져오는 것인데, 무슨 쿼리로 가져오는지 확인할수있다.
https://www.youtube.com/watch?v=aaC07qy3JXQ
질문하기와 qna목록이 나타나는 기능을 구현하겠다.
글쓰기 화면으로 이동하려면 html상의 form 페이지를 template으로 이동해ㅑ한다.
QuestionController클래스 만든후 애노테이션 @Controller 추가함으로써 컨트롤러 역할 하는 클래스임을 알려준다.
form.html에 접근할수있는 메소드 필요하다.
공통의 url을 requestmapping에 questions로 추가한다. 보통 복수로 한다.
post에 아무것도 없으면 생성..
로그인 안한상태에서 글쓰려면 글쓴이를 받아야하지만 로그인상태에서만 글쓸수있게한다면 글쓴이 form에서 삭제해도 된다.
if(!HttpSessionUtils.isLoginUser(session)){
return .users/loginForm}
와 같이 로그인페이지로 이동하도록할수있다.
qna/form에서 글쓴이 부분 지워도 된다.
제목과 내용은 얼마 안되니까 메서드의 인자로 받을수있다. 이 때의 url은 form에 메서드 post, action /questions로 두면 질문하기 버튼을 클릭했을 때 questions라는 url로 서버에 제목과 내용이 전달될 것이다.
@PostMapping("")
public String createQuestion(String title, String contents, HttpSession session){
if(!HttpSessionUtils.isLoginUser(session)){
return /users/loginForm; //로그인안된상태면 로그인 페이지로 이동시킨다.
}
User sessionUser = HttpSessionUtils.getUserFromSession(session);
//로그인된 유저가 누구인지 session으로부터 알아낸다.
return "redirect:/;
} 정상적으로 질문 등록되면 메인페이지(질문목록)으로 이동하도록한다.
HttpSession session을 메소드 인자로 받는 이유는 질문자가 로그인상태인지 판단해서 로그인상태에만 글쑬수있도록하기위해서이다. 글쓴이가 누군지도 session에서 알아내야한다.
질문에 대한 정보를 디비에 저장해야하니까 Question 클래스를 만들어서 @Entity로 매핑하겠다.
@Id @GeneratedValue private Long id;
//우리가 받을 정보인 String writer, title, contents를 private으로 안에 둔다.
우리가 이 정보를 매번 set할수있지만 question이 매번 생성될때 question에 저장할수있도록 생성자를 만들어주겠다.
JPA에선 매핑할때 인자를 받는 생성자가 있고 Question(String writer, String title...){}
, 디폴트 생성자를 필요로한다.Question(){}
기본생성자를 만들어줘야 문제가 발생하지 않는다.
Question도 하나의 table로 생성이 될것이다.
다음 단계로 db와의 작업을 담당할 repository를 인터페이스로 만들어야한다.
extends CrudRepository<Question, Long> Question의 key값이 Long이기 떄문이다.
-> Question question = new Question(sessionUser.getUserId, title, contents)
아ㅗ 같이 question을 만들수있다.
questionRepository를 그대로 사용하면 되고 autowired를 붙여주면 해당 repository를 그냥 사용할수있다. autowired애노테이션은 이 리파지토리를 스프링프레임워크를 관리하는데 사용하고싶으니 나에게 인자를 전달해달라고 하는 것이다. autowired없이 private UserRepository userRepository와 같이 하는 것은 NPE까 나는 것이 맞다. 그런데 @autowired가 있어서 이 리파지토리를 사용하려하니까 애노테이션붙은 값에다가 할당을 하라고 알려주는 것이다.
리파지토리는 인터페이스라서 구현체가 없다. 이에 대한 구현체를 생성해주는 부분이 스프링 프레임워크에서 해준다. 스프링 프레임워크에서 구현체를 생성하고 인스턴스까지 만들어서 관리해준다. 우리는 이 프레임워크가 관리하는 인스턴스를 이 필드에 할당하라고 (autowired를 통해) 스프링프레임워크에게 명령하고 가져다쓰면된다.
questionRepository에 save
인텔리제이 설정
Settings -> Build -> Compiler 에서 enable the Make Project Automatically 에 체크
Settings -> Build -> Gradle 에서 Build and run using 항목을 IntelliJ IDEA 로 설정
인텔리제이에서 액션 찾기로 registry 에 들어간 후 compiler.automake.allow.when.app.running 에 체크
Run/Debug Configurations -> Running Application Update Policies -> Update classes and resources 에서 On 'Update' action 을 Hot swap classes and update trigger file if failed 로, On frame deactivation 을 Update resources 로 설정
적용
타임리프 화면 띄우고 크롬에서 livereload 확장기능 버튼 클릭
intellij 에서 타임리프 파일 수정 후 브라우저로 창 옮기면 재시작 안해도 업데이트 확인 가능
액션찾기: ctrl + alt + a