Spring - MVC
11. 공지사항 컨트롤러 추가하기
ListController를 만들어보자.
url매핑을 해야된다. 어디서? dispatcher-servelt.xml
<bean id="/notice/list" class="com.newlecture.web.controller.notice.ListController"/>
http://localhost:8080/notice/list.html로 나온다. static의 html을 불러온것임.
잘못부른것임.
index에서 <a href="/notice/list.html">로 되어있어서 그렇다. <a href="/notice/list">
컨트롤러를 통해서 오게 된다.
12. Detail 컨트롤러 추가와 View 페이지 집중화의 필요성
<bean id="/notice/detail" class="com.newlecture.web.controller.notice.DetailController"/>
매핑을 해주고
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("notice/detail");
return mv;}컨트롤러에 연결한다.
그런데 각 페이지에서 어디론가 가자면 .html이 남아있다. header마다 그런데 하나씩 다 고치기 힘들다.
이것을 집중화해야한다. 모든 파일마다 수정하는게 힘들다. 집중하기 위해 tiles를 사용해야한다. 메이븐 라이브러리를 해야한다.
13. 페이지 공통분모 집중화
공통으로 나오는 부분을 집중화 하고자 한다.
컨텐츠를 가지고 잇는 페이지는 링크를 타고 제일 끝페이지에 있는다. 이런 유사한 컨텐츠들은 묶어두고 위의 링크에서는 하위페이지의 요약본 등이다.
페이지마다 공통으로 가진 부분이 잇다. header영역같은게 있다면 분리해서 공통으로 참조하도록 해야한다.
공통 부분을 수정하게 되면 모든 페이지를 다 수정해야하니 공통부분을빼고 하면좋다. 그런데 jsp:include로 하면? 중복되서 넣어야하는 것이 이것마저도 불편해진다.
이것마저 쉽게하는 것이 tiles 이다.
14. 페이지 모듈 분리하기
페이지마다 반복되는 header부분과 footer부분을 잘라내서 inc(include)폴더를 만들고 따로 jsp파일을 만들어준다.
list페이지와 detail페이지에서도 각각 지워준다.
list페이지와 detail페이지 '비주얼' 부분도 같다.
그런데 원래 inc는 페이지 전체의 것이기 때문에 고객센터와 관련된 부분만 따로 나누고 싶다.
customer/inc폴더를 만들고 visual.jsp를 만들어주자.
notice파일 자체도 고객센터 기능이기때문에 같이 합쳐주자. 이외의 기능들을 추가해나갈때 합쳐나갈 피룡가 있으니 구분을 잘해두면 좋다.
이런것들을 하나하나 조립해나갈것이다.
남아있는 것은 layout.jsp로 두고 list.jsp에는 메인 부분만 남긴다.
detail도 main만 남긴다.
이런식으로 공통인 부분을 다빼고 남겨두면 모듈화를 시키게 되는 것이다.
15. Tiles 지시서 작성하기
다쪼개서 담아놧는데 이것을 엮기 위해 Tiles를 이용할것이다.
그런데 apache.org에서 프로젝트를 다운받아야한다. 그런데 종료된 서비스이다?
직접 찾아보자. 페이지를 백엔드에서 아니라 프론트에서도 만든다. 점점 소규모 프로젝트는 프론트에서 만들고 합치는 작업을 백엔드에서 하는 양이 줄어드니 타일즈가 없어졋다.
더이상 합칠일이 줄얻르엇다. 그런데 백엔드에서도 만드는 일이 잇으니 배워놓을 필요가잇다.
https://attic.apache.org/projects/tiles.html
설정방법을 알려줌
https://tiles.apache.org/framework/tutorial/basic/pages.html
/WEB-INF/tiles.xml에 둔다고 하니 만들고 넣자.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definition name="myapp.homepage" template="/layouts/classic.jsp">
<put-attribute name="title" value="Tiles tutorial homepage" />
<put-attribute name="header" value="/tiles/banner.jsp" />
<put-attribute name="menu" value="/tiles/common_menu.jsp" />
<put-attribute name="body" value="/tiles/home_body.jsp" />
<put-attribute name="footer" value="/tiles/credits.jsp" />
</definition>
</tiles-definitions>여기서 definition name="myapp.homepage" 컨트롤러가 반환할 이름이다.
package com.newlecture.web.controller.notice;
public class ListController implements Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("notice.list");
return mv;
}}notice/list로 리스트를 불러왓는데 매핑되도록 이름을 맞춰서 만들어준다. notice.list 반드시 '.'일필요는 없는데 일반적으로 이렇게 하는게 패턴만들기 편하다.
tiles도 <definition name="notice.list" template="/layouts/classic.jsp"> 여기도 맞춰서 변경해준다.
그런데? 그럼 페이지마다 하나씩 있어야 만들어줘야하나? 그렇다.?
url마다 하면 불필요하게 코드가 늘어나는 것 같다. 일단 이번에는 이렇게하고 나중에 합치고자 한다.
layout.jsp를 기반으로 하나씩 붙여나갈겉이다.
<definition name="notice.list" template="/WEB-INF/view/customer/inc/layout.jsp">
<put-attribute name="title" value="Tiles tutorial homepage" />
<put-attribute name="header" value="/WEB-INF/view/customer/inc/header.jsp" />
<put-attribute name="visual" value="/WEB-INF/view/customer/inc/visual.jsp" />
<put-attribute name="aside" value="/WEB-INF/view/customer/inc/aside.jsp" />
<put-attribute name="body" value="/WEB-INF/view/customer/notice/list.jsp" />
<put-attribute name="footer" value="/WEB-INF/view/customer/inc/footer.jsp" />
</definition>이런식으로 각각 지정해준다.
detail도 레이아웃이 같기때문에 비슷하게 설계해준다.
이제 얘들을 어떻게 설정을 할 것인가? 이것은 다음강의
16. 레이아웃 페이지 만들기와 Tiles 라이브러리 설정하기
Tiles지시서와 dispatcher 지시서가 있는 것이다.
이 레이아웃에서 어디에 포함할 것인지를 정해야한다.
Tiles가 제공하는 태그 라이브러리를 사용해야한다.
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>3.0.8</version>
</dependency>
메이븐에서 다운받아준다. 내장검색기능을 사용하고 싶지만 sts,이클립스 최신버전의 오류때문에 직접가서 찾아야한다.
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
layout.jsp에 태그를 추가해주자.
헤더부분에 <tiles:insertAttribute name="header"/>을 넣어주자.
각각 넣어준다. 지시서의 이름과 같게 넣어줘야한다.
위의것처럼 파일을 넣을 수잇고
<tiles:getAsString name="title"/> 이런식으로 문자열로만 사용할 수도 있다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><tiles:getAsString name="title"/></title>
<link href="/css/customer/layout.css" type="text/css" rel="stylesheet" />
<style>
#visual .content-container{
height:inherit;
display:flex;
align-items: center;
background: url("../../images/customer/visual.png") no-repeat center;
}
</style>
</head>
<body>
<!-- header 부분 -->
<tiles:insertAttribute name="header"/>
<!-- --------------------------- <visual> --------------------------------------- -->
<!-- visual 부분 -->
<tiles:insertAttribute name="visual"/>
<!-- --------------------------- <body> --------------------------------------- -->
<div id="body">
<div class="content-container clearfix">
<!-- --------------------------- aside --------------------------------------- -->
<!-- aside 부분 -->
<tiles:insertAttribute name="aside"/>
<!-- --------------------------- main --------------------------------------- -->
<tiles:insertAttribute name="body"/>
</div>
</div>
<!-- ------------------- <footer> --------------------------------------- -->
<tiles:insertAttribute name="footer"/>
</body>
</html>17. Tiles ViewResolver 설정하기
레이아웃을 완성했다. jsp를이용해서 접두사와 접미사를 만들었는데 타일즈를 사용해서 합친것을 찾아주는 역할을 하는 게 있어야한다.
이것을 연결해주느 것이 Tiles ViewResolver이다.
그래서 지시서를 찾아야하는데 지시서를 어떻게 찾느냐? tiles.xml을 넣어주면된다.
<bean
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.tiles3.TilesView" />
<property name="order" value="1" />
</bean>
<bean
class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions" value="/WEB-INF/tiles.xml" />
</bean>
그럼 Resolver가 두개가 있게 되는데 이 것들이 싸우면안된다.
<property name="order" value="1" />를통해서 순서를 정해주면된다.
localhost:8080/notice/list하면 에러가 나온다. 클래스를 찾을 수없다.
<!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
javax.servlet - jstl을 다운받아줘야한다.
!!에러발생!!
"하나 빼먹음.
<tiles:insertAttribute name= "body"/>
<put-attribute name="footer" value="/WEB-INF/view/inc/footer.jsp" />
헤더와 푸터 설정시 루트를 cusomter/inc로적음
에러메시지지를 하나씩 보면 왜 낫는지 알게되니 천천히 읽어보자.
18. Tiles 설정에 Wildcard 이용하기
tiles.xml 할때 하나하나 지정해줘야해서 비효율적이게 느껴진다.
거의똑같은데 notice.이부분만 다르다. 뭐가오든 걸리게 notice.*로 해주자.
첫번째 와일드 카드값이 오면 이 값이 다시 또 사용할수잇도록
<put-attribute name="body" value="/WEB-INF/view/customer/notice/{1}.jsp" />
로 만들어주면 첫 *에 온값이 {1}에 들어가게된다.
와일드카드가 만약 여러개이면 {2}..하나씩 추가할 수 있게된다.
와일드카드에 대한 방법은 여러가지 방법이 있는데 타일 사이트에 들어가면 사용방법이 있다.
https://tiles.apache.org/framework/tutorial/advanced/wildcard.html
와일드카드를 정규식으로 사용할 수도 있다. 실질적으로 사용할때는 별로 사용되지 않는다.
19. Root 페이지들을 위한 Layout 페이지 만들기
와일드카드를 이용해서 패턴을 만들었다.
인덱스 페이지에 헤더 푸터가 없다. 루트페이지는 리스트나 디테일과 다른데 어떻게 설정할 것인가?
인덱스페이지에 헤더푸터 붙여보자. 루트에 있는것만 만들고자한다
WEB-INF/inc 에 layout.jsp를 하나더 만들어주자.
index.jsp 에는 컨텐츠 내용만 남겨 놓는다.
루트에 있는것을 부르기위해 그냥 *만쓰면 notice밑에있는것이 통과되지않는다.
<definition name="root.*" template="/WEB-INF/view/inc/layout.jsp">
<put-attribute name="title" value="공지사항" />
<put-attribute name="header" value="/WEB-INF/view/inc/header.jsp" />
<put-attribute name="body" value="/WEB-INF/view/{1}.jsp" />
<put-attribute name="footer" value="/WEB-INF/view/inc/footer.jsp" />
</definition>public class IndexController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("root.index");
return mv;
}
}그냥 index가 아니라 root.index로 해주자.
다음 장부터는 스프링MVC가 입력부분을 해보도록 하자.
20. 데이터 서비스 클래스(NoticeService) 준비하기
뷰에 심을 수잇는 데이터를 담아야 한다.
noticeService와 notice객체 가져오기.
db에 테이블 만들기
21. 서비스 객체 사용하기

db를 바인딩해서 데이터를 쿼리하고 결과물을 볼 수 있도록 하자.
라이브러리가 필요하다. 또다시 pom.xml에 추가해주자.
<!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>21.8.0.0</version>
</dependency>
더 위버전이 있지만 그냥 항상쓰던거 쓰자.
NoticeService를 어떻게 생성할 것인가? 직접생성하는 것보다 di로 생성하는게 좋을 듯하다.
<bean class="com.newlecture.web.service.NoticeService"/>
을 넣어주면 주문표로 IOC컨테이너에 담아주게 된다. 전역적으로 사용하는 객체 보따리이기 때문에 컨트롤러에서 가져다 쓸 수 있게된다.
이걸 이제 DI하면된다.
<bean id="/notice/list" class="com.newlecture.web.controller.notice.ListController">
<property name ="noitceService" ref="noitceService"/> //setNoticeService인데 없애고 noticeService하는것임.
</bean>
그리고 컨트롤러에 세터를 만들어주자.
private NoticeService noticeSerivce;
public void setNoticeSerivce(NoticeService noticeSerivce) {
this.noticeSerivce = noticeSerivce;
}
dispatcher를 읽으면서 서비스 객체가 만들어지고 컨트롤러에 세팅되서 컨트롤러가 활용하게 되는 것이다.
jsp때 만들었던 public List<NoticeView> getNoticeViewList(); 와 같은
public List<Notice> getList(); 메소드를 불러와서 사용한다.
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView("notice.list");
List<Notice> list = noticeSerivce.getList(1, "TITLE", "");
mv.addObject("list", list);
return mv;
}페이지1 필드 TITLE 쿼리는 모든것을 받아서 뽑아낸다. 반환값이 LIST니까 list로받는다.
모델에 list를 view가 list로 담아준다.
list.jsp로가서 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
를 이용해서 jstl태그를 사용하도록 하자.
반복해서 나타내고 el로 값을 넣어주자.
<c:forEach var="n" items="list">
<tr>
<td>${n.id}</td>
<td class="title indent text-align-left"><a href="detail">${n.title}</a></td>
<td>${n.writerId}</td>
<td>
${n.regdate}
</td>
<td>${n.hit}</td>
</tr>
</c:forEach>jsp 에서 배운내용이다.
NotWritablePropertyException 세터가 있냐고 물어보는 에러발생함..?
컨트롤러의 세터이름을 잘못 설정한 결과였다.
n.id가 int로와야하는데 String으로 잘못와서 에러가난다. 정상적으로 오지않았다는것임.
<c:forEach var="n" items="list"> 여기서 문자열로 가지고 와서 안되는 것이다.
<c:forEach var="n" items="${list}"> el태그로 데이터를 빼와서 전달받아 오도록해주자.
또 에러발생 이번에는 regdate가 없다고 나옴. -> 확인하니 notice객체에 regDate라고 되어있었음. 동일하게 써주는 것을 신경쓰자.
22. 서비스 객체 분리하기
서비스객체로 데이터베이스를 연동했다. 스프링을 이용할때는 서비스가 직접 컨트롤러를 사용하지 않는다 분리해야한다.
결합력을 낮춰야한다.
컨트롤러 멤버변수로 service:JDS <- JDBCService(JDS) - db를 직접활용해서 사용했다.
만약 이렇게 사용하다가 DB를 바꾸거나 JDBC를 사용하지 못하는 상황이 왔다고 생각해보자 서비스를 다시 구현해야한다.?
service:JDS 를 했엇기 때문에 이것을 고쳐줘야하는데 결합력이 매우 강한 상태가 된것이다.
이것을 직접사용하지 않고 Interface Service를 활용해서 service:S 기능에만 포커스를 맞춰서 사용하면 구현객체가 바뀐다고 해도 문제가 생기지 않는다.
어떤객체를 구현했느지 상관을 안하게 하는 것이다. 이꽃는걸 xml로 수정하면된다.
일단 구분을 위해 JDBCNoticeService로 바꾸고 service.jdbc패키지를 만들어서 옮겨주자.
다른 db를 사용할 수도 있다는 것을 보여주기 위해 다른 것도 추가해보자.
사실 이것도 다르다. 데이터베이스 구현체가 서비스에 들어가지 않는다. 나중에는 DAO라는 객체로 db와 연결될 수 있도록한다.
구현객체를 사용할 NoticeService 인터페이스를 추가해준다.
package com.newlecture.web.service;
import java.sql.SQLException;
import java.util.List;
import com.newlecture.web.entity.Notice;
public interface NoticeService {
List<Notice> getList(int page, String field, String query) throws ClassNotFoundException, SQLException;
int getCount() throws ClassNotFoundException, SQLException;
int insert(Notice notice) throws SQLException, ClassNotFoundException;
int update(Notice notice) throws SQLException, ClassNotFoundException;
int delete(int id) throws ClassNotFoundException, SQLException;
}구현객체가 된것이다. 나중가선 DAO를 사용할 것이니 JPA같은건 납둔다.
인터페이스 활용 이유를 알기 위해서 가짜로 만들어 놓은 것이다.
그런데 인터페이스를 넣고 객체를 정의해서 꽃아야된다. xml파일에서 di를 해줘야한다.
<bean id="noticeService" class="com.newlecture.web.service.jdbc.JDBCNoticeService"/>
꽃아넣을게 객체인데 세팅하는 함수의 자료형이 클래스가아닌 인터페이스가 된다.
설정의 클래스부분만 바꿈으로서 전체코드가 바꿀필요가 없다.
23. 연결정보 분리하기
NoticeService를 사용하고 있는 데이터 연결정보를 별도의 클래스로 분리하자.
나는 원래 jdbc를 연결할때 "oracle.jdbc.OracleDriver"를 사용햇는데
예제에는 "oracle.jdbc.driver.OracleDriver"로 드라이버가 작성되어 있어서 차이점이 궁금해서 찾아보았다.
후자는 높은 버전에서는 deprecated된것이다. 전자를 사용하도록 하자.
https://hanshinkyj.tistory.com/4
db에 연결문자열 id pwd가 있는데 서비스를 배포할때 소스코드를 배포하는게 아니라 컴파일된 bin를 배포해야한다.
비밀번호를 주기적으로 바꿔야하는데 바뀌었을때 단순하게 비밀번호를 바꿔야하는데 소스코드없이 단순히 뱁포하느넥 불가능하다. 다시 컴파일해서 배포해야되는데 하나씩 다바꿔서 배포하는것은 문제가 잇을수잇다.
또한 위치가 바뀌엇을때 localhost가아니라 다른곳일때 문제가생긴다.
이것으르 xml에 객체화해서 di하면 텍스트 그대로 배포되기때문에 문제가 안생길 수 있다.
물론 문자열 4개로 담아서 할 수 잇긴하지만 4개정보를 하나의 클래스로 담은게 더 편할 것이다.
이 데이터 정보를 담을 수 있는 인터페이스가 있다. 이것이 데이터 소스이다. private DataSource dataSource;
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}di할수잇도록 세터도 만들어주자.
기존의 커넥션을 주석처리해주고 dataSource로 getConnection()해주면된다.
//Class.forName(driver);
//Connection con = DriverManager.getConnection(url,uid, pwd);
Connection con = dataSource.getConnection();
DataSource dataSource 이 null값이니 어떻게 꽃아넣을건지 만 하면된다.
<bean id="noticeService" class="com.newlecture.web.service.jdbc.JDBCNoticeService">
<property name ="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DricerManagerDataSource"/>di햇던것처럼꼽아넣어준다.
차이점이라면 이 데이터소스에다가 정보를 꽃아넣어줘야한다.
DataSource 클래스가 가지고 있는 setter네임으로 해야한다.
<bean id="dataSource" class="org.springframework.jdbc.datasource.DricerManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521/orcl"/>
<property name="username" value="java"/>
<property name="password" value="oracle"/>
</bean>문자열이기때문에 value로 꽃아넣어준다. 만약 비밀번호가 바뀌었다면 여기서 설정만 수정해주면 되는 것이다.
Spring JDBC 리포지터리를 추가해줘야 사용할 수 있다.
<dependency>
<groupId>org.springframework
<artifactId>spring-jdbc
<version>5.2.22.RELEASE
</dependency>
또 오류발생 클래스명을 잘못적음! 오타확인하자. 이번에도 오류내용을 읽어서 문제를 확인했다.
24. 스프링 설정파일 분리하기
dispatcher-servlet.xml을 바꿔보자
왜 바꾸나?
정해진 이름을 사용햇다. 정해진위치에 둿다. 다른위치 다른 이름으로 두면 인식이 안됫다.
근데 일반적으로는 원하는 위치와 파일명으로 만들 수 있다.
spring폴더를 만들어주고 복사하고 3개로 나누어준다.
서블릿과 관련된 servlet-context / 서비스와 관련된 service-context / 보안과 관련된 security-context 으로나눈다.
파일명이 정해진것은 아니고 마음대로 작성하면된다.
근데 굳이 나눈 이유는 뭘까? 프로젝트를 한사람이 만드는게 아니라 여러사람이 만든다. 각각역할을 부여해서 만들기때문에 나눠서 만드는 일이 잇다.
덮어쓰기 하다가 설정을 망가뜨리거나 작업을 기다리는 동기화 작업을 하면 매우 비효율적이다. 따로 설정을 가지고 있는 것이 바람직하다.
혼자 만든다고 하더라도 나눠서 정리하는 것이 좋다. 역할자가 나눔에 따라서 더 많이 나눌 수 있다.
web.xml로가서 설정을 해줘야한다. 설정파일위치과 이름을 정해놓지 않았기 때문에 WEB-INF에 넣어야했던것이다.
하나의 파일밖에 정할 수 없다. 그러면 어떻게 해야하나? 스프링이 제공하는 listner가 있다.
톰캣이 시작되거나 세션이 끝날때 등에 이벤트를 지정할 수 있게 해준다. 파라미터값을<context-param>으로 가질수잇다.
ContextLoaderListener이 정하는 파라미터 설정을 DispatcherServlet이 사용할 수있다.
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/service-context.xml
/WEB-INF/spring/security-context.xml
</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextCofigLocation</param-name>
<param-value>/WEB-INF/spirng/servlet-context.xml</param-value>
</init-param>
</servlet><servlet></servlet>이 언제 실행이되나? 메모리에 언제 올라가나? url이잇고 그 매핑이 요청이 오면 메모리에 올라간다.
그런데 설정을 다루고 있는 것이기때문에 요청이 올때 메모리에 올라가게 되면 느린속도를 보여주게 된다. 그러면 미리 올라가야하지 않을까?라고 생각하게된다.
톰캣이 시작될때 로드하도록 해주자.만약 이서블릿이 여러개라면 숫자를 넣어서 우선순위를 넣어주자.
<load-on-startup>1
이녀석이 로드가 되는것을 다른것들과 동기되서 하나씩 로드하는 것을 겪게 할 수잇지만 비동기적으로 로드하는 것을 원하면
<async-supported>true
각각 밑에 넣어주면된다.
2023.01.17 후기
점점 페이지가 갖춰지고 있다. xml에서 하나씩 올라가면서 배우면 나중에 더 잘 이해할 수 있을 것 같다.
중요한 것은 꺾이지 않는 마음.
'기초단계 > SPRING' 카테고리의 다른 글
| 2023.01.19 Spring (0) | 2023.01.20 |
|---|---|
| 2023.01.18 Spring (0) | 2023.01.19 |
| 2023.01.16-2 Spring (1) | 2023.01.16 |
| 2023.01.16-1 Spring (0) | 2023.01.16 |
| 2023.01.15-2 Spring (0) | 2023.01.15 |