웹 개발 Spring Day3 Thymeleaf, 정규식, Service, @Autowired, 의존성 주입

김지원·2022년 7월 29일
0

WebDevelop2

목록 보기
22/34

서버를 돌리지 않아도 웹에서 보았을 때 수정된 내용이 바로 적용되도록 하는 방법 (보통 프로젝트를 생성할 때 미리 다 설정한다.)

  • 여기에는 tomcat(+spring)을 실행시킬때의 설정들이 들어있다.

  • dev.jwkim.mvc.MvcApplication 이 시작점인걸 알려준다.

  • update classes and resources 두개 추가

  • 반환타입을 String으로 써야하지만 보통 ModelAndView을 많이 사용한다.

  • "home/index" 리턴한다.

ModelAndView 을 사용하면 html에서 컨트롤러로 값을 넘기기에 용이하다.

  • 참고) body바로 아래 자식에 있는 id는 css에서 사용해도 괜찮다.
    id는 하나만 있을 걸 알기 때문에 속도면에서 빠르다.

전송버튼을 누르면 home/index 로 요청이 나가게 된다.


action

form에 action 속성이 없으면 현재 주소로 요청을 보낸다.
form에 action 속성이 있으면 그 주소로 요청을 보낸다.

form태그에 action을 주지않고 post요청을 하려면 동일한 맵핑메서드를 만들면 된다.

전송버튼 누르고 보여주는 화면은 POST가 보여주는 페이지이다.
새로고침을 해서 다시 보는 화면또한 POST 요청일것이다.

  • 새로고침을 했을 때 똑같은 내용이 뜬다는 것을 확인해보자
    즉, GET요청이 아니라 POST요청으로 계속 돌아가고 있다는 것을 확인해보자.
    새로고침방법이
    데이터를 이미가지고있다.

  • 현재 html / css 결과

메세지를 배열에 담자.

private String[] messages = new String[] {};

String[] oldMessages = messages;
	message = new String[messages.length + 1];
	for (int i = 0; i < oldMessages.length; i++) {
		message[i] = oldMessages[i];
 }
 message[message.length -1] = message;
for (String m: messages) {
	System.out.println(m);
}
=
messages.forEach(x -> {
System.out.println(x);
});
=
messages.forEach(x -> System.out.println(x));
// 람다(lamda) 표현식
=
messages.forEach(System.out::println);

messages.forEach(System.out::println);

: 이중 콜론 연산자, 메서드 참조 표현식,

[객체]::[메서드]

목적은 줄이기 위해서 사용한다.
Collector할때도 사용하긴 한다.

[객체]::new

기초타입의 배열이다. 메모리에 쌓이는 방식은 참조타입이랑 같다. - java Stream API

int[] aa = null;
int[] numbers = new int[]{1,2,3,4,5};
int[] evens = Arrays.stream(numbers).filter(x -> x % 2 == 0).toArray();


Thymeleaf

  1. <html>태그에 xmlns:th="http://www.thymeleaf.org" 라는 내용을 추가하여 해당 HTML 문서에서 Thymeleaf 문법을 사용하겠다 라고 알려준다.
  • 실제로 Thymeleaf에 쓴 내용은 클라이언트에게 전달 되지 않는다.
  1. ${}은 Java문법을 사용하고자 할 때 사용한다.

  • 소스보기를 하면 8만 뜨는 것을 확인할 수 있다.
  1. 속성
  • th:text="내용" : 해당하는 태그의 내부 내용을 해당 속성 값으로 한다.
  • th:each="item(원소이름) : ${arr}" : 'arr' 배열 혹은 리스트 등으로 부터 반복할 수 있는 원소인 'item' 에 대해 해당 태그를 반복한다.

제네릭 타입
String 반복이 될 것이다.

새로고침을 해도 남아있다.

둘 다 비정적인 메서드고 속해있는 클래스내에 호출하는데 문제가 없다.


study-database 프로젝트 생성
MVC 패턴으로 코드 작성하면서 데이터베이스를 연결해보자.

  • controller의 이름을 붙여주는 것이다.

  • ModelAndView 를 return 던지기 위해 ModelAndView 를 직접 객체화 하지 않아도 매개변수로 적게되면 spring이 알아서 객체화해준다.

< 자바 스크립트 코드 정리 >

내용을 적지 않으면 form 요청이 나가지 않고 warned를 뜨게 한다.

<script>
const main = window.document.getElementById('main');
const form = main.querySelector(':scope > .form');
const writerRegex = new RegExp('^([가-힣]{2,5})$'); // RegExp : Regular Expression(정규식) 타입
form.onsubmit = () => {
    if(form['writer'].value === '' || !writerRegex.test(form['writer'].value)) {
        form['writer'].classList.add('warned');
        form['writer'].focus();
        return false;
    }
    if(form['content'].value === '') {
        form['content'].classList.add('warned');
        form['content'].focus();
        return false; // 내용을 적지 않으면 form 요청이 나가지 않음.
    }
    return true; // 자바스크립트에서는 안적어줘도 되지만 명시적으로 해준다.(자바에서는 말이 안되기때문에)
};
</srcipt>

RegExp : Regular Expression(정규 표현식) 타입

입력을 하게 되면 warned를 사라지게 한다.

<script>
form['writer'].addEventListener('keydown', () => { // 입력을 하게 되면 warned 사라진다.
    if(form['writer'].classList.contains('warned')) {
        form['writer'].classList.remove('warned');
    }
});

form['content'].addEventListener('keydown', () => {
    if(form['content'].classList.contains('warned')) {
        form['content'].classList.remove('warned');
    }
});
</srcipt>

return false; 을 하게 되면

: form 요청이 나가지 않는다.
매개변수 e 받아서 e.preventdefault 하고 return 해줘도 된다.

keydown : 키를 누르는 것.
키를 누르면 다시 올라온다. 그때는 keyup이다.


완성 한글 2자~5자까지만 되게 하도록 정규식을 사용해보자!

정규식

정규표현식(Regular Expression)은 언어의 종류와 관계 없이 주어진 문자열에 대해 정규화 (Noramlize) 를 하기 위한 공통된 문법

 ^ : 문자열의 시작
 $ : 문자열의 끝
 () : 그룹
 [] : 표현 범위
 {} : 길이
^( )$
  ↑  완성한글 && 2자~5자가 되는지 확인한다.
`홍길동` 이라는 문자열의 시작부터 끝까지를 검사한다.
^([가-힣]{2,5})$
  • 유니코드로 보았을 때 가 시작이고 이 가장 마지막이다.

여기서 나의 정규식이 올바르게 작동하는지 체크할 수 있다.

String name = "홍길동";
return name.matches("^([가-힣]{2,5})$"); 

자바에서는 이렇게 사용한다.

  • 정규식에 맞게하지 않으면 작성자를 입력해달라고 뜨게 된다.
  • 어차피 빈문자열이면 정규식 통과를 못하기 때문에 지워주는 것이 깔끔한 코딩이 된다.

  • 현재 값을 입력하고 submit을 하게 되면 오류가 발생한다.
    주소는 있는데 그 요청을 받는 맵핑이 없기 때문에 405가 뜬다.
  • POST 맵핑 생성.

POST 맵핑으로 값이 두개가 넘어온다. 개발자 도구를 통해서도 확인이 가능하다.

  • writer / content 가 Requestparam의 value가 되고
    html의 Input의 name이 될 것이다. 이 두개가 일치해야 값이 넘어온다.

  • value 와 name이 일치하기에 값이 잘 넘어온다.

넘어온 값을 DB에 담아보자.

root말고 다른 계정을 생성하고 삭제하고 수정하는 방법

CREATE USER `이름`@`호스트` IDENTIFIED BY '비밀번호';
DROP USER `이름`@`호스트`;
ALTER USER `이름`@`호스트` IDENTIFIED BY '새 비밀번호';
GRANT 권한,... ON `스키마`.`테이블` TO `이름`@`호스트`;
-- 특정 스키마/테이블에 대해 명시한 유저에게 명시한 권한을 준다.
REVOKE 권한,... ON `스키마`.`테이블` FROM `이름`@`호스트`;
-- 특정 스키마/테이블에 대해 명시한 유저에게 명시한 권한을 뺏는다.

호스트(Host) 자리가 의미하는 것은 앞에 적어놓은 사용자의 이름으로 접속을 할수 있는 원격 호스트를 제한하는 것이다.
즉, root@localhost라는 계정으로는 그 DB서버가 설치되어있는 컴퓨터로만 접근이 가능하다는 것이다.


예시를 들어보겠다.
본 컴퓨터에서 사용자를 두개 생성을 했다.

CREATE USER `study`@`localhost` IDENTIFIED BY 'test1234';
CREATE USER `study`@`%` IDENTIFIED BY 'test1234';
GRANT ALL ON *.* TO `study`@`localhost`;
FLUSH PRIVILEEGES;

본 컴퓨터에만 SELECT 권한을 주었다.

본 컴퓨터 : study → SELECT ~ → 정상 실행

본 컴퓨터가 DB가 바라본 본 컴퓨터의 접근은 본 컴퓨터에서 하는 것이 맞다라고 한다. study@localhost 로 접속이 된다.

외부 컴퓨터 : study → SELECT → ~SELECT command denied ~ : 거절이 되었다 라고 뜨게 된다.

본 컴퓨터의 DB에서 보았을 때 외부 컴퓨터 (본 컴퓨터의 DBMS가 없는)의 접근은 외부인으로 간주한다.
study@ 192.168.1.254 로 접속이 되는 것이다.

즉, 아이디가 같더라도 Host에 따라 권한을 다르게 줄 수 있다 라는 것이다.

@ 뒤로 부터는 마음대로 지정할 수 없기 때문이다.


터미널로 넘어가자.

  • mysql -u root -p -P 3306 -> password 입력
CREATE USER `study`@`localhost` IDENTIFIED BY 'test1234';
GRANT ALL ON *.* TO `study`@`localhost`;
FLUSH PRIVILEEGES; // 변경 된 권한 정보 업데이트
  • 터미널에서 이거 해주면 사용자 생성이 되는 것이다.

  • 다 작성후 Test Connection -> Succeeded

문의내용 담는 테이블 생성

테이블이 가지는 열에 대한 1:1 매칭 멤버변수 생성해야한다.
그리고 getter, setter 생성
: 캡슐화

-> entities > InquiryEntity

위의 Entity에서 index랑 created_at은 알아서 값이 정해지기 때문에 나머지 두개에 대해서만 받아주면 된다.
html파일에서는 writer, content의 값이 넘어오게 된다. 컨트롤러 포스트로 넘겨주자.

InquiryEntity inquiry = new InquiryEntity();
inquiry.setWriter(writer);
inquiry.setContent(content);

  • 웹에서 입력한 값이 객체화된 Entity를 통해 컨트롤러로 넘어오고 정상적으로 출력이 된다.

객체화하고 값을 지정하고 불러오는게 귀찮으면 @RequestParam 대신 Entity를 매개변수 자리에 넣자.

  • 어디에도 writer, content에 대한 언급이 없는데 똑같이 값을 잘 출력한다.

왜 이렇게 되는지 알아보자.

작성자를 가지고 있는 html input태그의 name은 writer이다.

작성자
writer -> Writer (파스칼케이싱) -> setWriter메서드를 호출하는 것이다.
문의 내용
content -> Content -> setContent

Entity의 매개변수 또한 input name이 같아야 작동을 한다.


하나는 수동으로 해줘야할 것이 있다.

  • InquiryEntity의 Date의 기본값이 현재시각이다 라는 것은 아래에 명시가 되어있지만 inquiry.setCreatedAt(new Date());을 해주지 않으면 null이 들어가게 됨으로 방지해준다. new Date() : 현재시간

다른 방법 ) 멤버변수에 초기화를 해주면 위처럼 안해도 된다.


Servie를 만들자.
Servie 또한 Controller에서 Controller어노테이션을 달아준것처럼 Servie도 Servie어노테이션을 달아줘야한다. Servie임을 알려주자.

@Service

value : 서비스 이름

: 해당 클래스를 서비스(Service)로 사용될 것 임을 스프링 부트에게 알린다.

의존성 주입!!
: HomeController를 이용하려면 스프링 너가 알아서 InquiryService로 객체화해서 집어 넣어놔라 라고 해주는 것이다.

  • HomeController의 멤버상수로 만든다
private final InquiryService inquiryService;

멤버상수는 선언만 해놓으면 안되고 선언과 동시에 값을 초기화 하던가 아니면 생성자를 생성자를 통해서 값을 집어넣어야한다.

InquiryService inquiryService = new InquiryService();

생성자를 이용하자.

@Autowired
public HomeController(InquiryService inquiryService) {
	this.inquiryService = inquiryService;
}

  • InquiryService를 매개변수 타입으로 가지는 생성자 생성.

그리고 @Autowired 어노테이션을 붙여준다!

@Autowired {Method | Variable}

: 해당 어노테이션이 적용되는 범위 내에 있는 것에 대해 스프링 부트가 알아서 객체화를 하도록 유도한다. 단, 해당되는 타입은 스프링 부트가 인식하고 있어야한다.

  • inquiryService라는 매개변수에 전달되어야하는 전달인자를 인식가능한 범위내에서! 스프링 부트가 알아서 InquiryService을 객체화해서 전달해준다.
  • @Service 어노테이션을 붙임으로써 서비스임을 알려줬기 때문에 스프링이 인식가능한 것이다. 즉, @Service 어노테이션이 없다면 불가능하다.

이런것을 의존성 주입이라고 한다.

의존성 주입(Dependency Injection)

: HomeController는 InquiryService에 의존적임
왜냐하면 HomeController의 생성자는 InquiryService 밖에 없기 때문에 InquiryService 없이는 만들어 질 수 없다.

HomeController homeController = new HomeController(...);

이렇게 새로 만들으려면 ...에 InquiryService을 주는 방법밖에 없다.
그렇게 되어도 HomeController는 InquiryService에 의존적이다.
...에 InquiryService을 넣어주지 않으면 HomeController는 객체화 될 수 없다.

Controller < Service < Repository 방향으로 의존적이다.

객체화가 완료 된 시점은 메서드의 실행의 종료이다.

profile
Software Developer : -)

0개의 댓글