20220602 복습

2022 - 0602 - 복습


package com.hanulso.controller; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * Handles requests for the application home page. */ @Controller public class HomeController { private static final Logger logger = LoggerFactory.getLogger(HomeController.class); /** * Simply selects the home view to render by returning its name. */ @RequestMapping(value = "/", method = RequestMethod.GET) public String home() { return "index"; } }


package com.hanulso.domain; import java.sql.Date; //util.Date도 된다??? (Date regdate) import lombok.Data; @Data public class NoticeVO { private int bno; private String title; private String content; private String writer; private Date regdate; private int hits; }


package com.hanulso.utility; import lombok.Data; import lombok.ToString; @Data @ToString public class Criteria { private int pageNum; private int amount; private String type; private String keyword; public Criteria() { //생성자 this(1,10); } public Criteria(int pageNum, int amount) { //생성자 this.pageNum = pageNum; this.amount = amount; this.type = type; this.keyword = keyword; } public String[] getTypeArr() { return type == null ? new String[] {} : type.split(""); } }


package com.hanulso.utility; import lombok.Getter; import lombok.ToString; @Getter @ToString public class PageVO { private int startPage; private int endPage; private int blockPerPage = 5; // 한 Page 당 보여줄 Block 수 private boolean prev, next; private int total; private Criteria cri; // Criteria 객체를 맴버변수로 활용. public PageVO(Criteria cri, int total) { // 생성자. this.cri = cri; this.total = total; this.endPage = (int)(Math.ceil(cri.getPageNum() / (double)(blockPerPage))) * blockPerPage; this.startPage = this.endPage - (blockPerPage - 1); int realEnd = (int)( Math.ceil((total * 1.0) / cri.getAmount()) ); if(realEnd < this.endPage) { this.endPage = realEnd; } this.prev = this.startPage > 1; this.next = this.endPage < realEnd; } }


package com.hanulso.domain; import java.util.Date; import lombok.Data; @Data public class PortfolioVO { private int bno; private String title; private String content; private String uploadfile; private Date regdate; private int viewcount; }

< Spring 기초 >




>>> pom.xml
12라인 -> 5.0.7
64라인 -> 1.2.17
116라인 -> 4.12
139라인 -> 3.5.1
141라인 -> 1.8
142라인 -> 1.8
-----------------------------------------------------------------------------------------------------
>>> root-context.xml

<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">

     <property name="driverClassName"
          value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
     <property name="jdbcUrl"
          value="jdbc:log4jdbc:oracle:thin:@localhost:1521:XE"></property>
     <property name="username" value="db53"></property>
     <property name="password" value="123456"></property>

</bean>

<!-- HikariCP configuration -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
     destroy-method="close">
          <constructor-arg ref="hikariConfig" />
</bean>

<bean id="sqlSessionFactory"
     class="org.mybatis.spring.SqlSessionFactoryBean">
          <property name="dataSource" ref="dataSource">
</bean>

<mybatis-spring:scan
     base-package="com.jslhrd.mapper" />

<context:component-scan
     base-package="com.jslhrd.service">

-----------------------------------------------------------------------------------------------------
>>> buildpath
mysql-connector-java-8.0.28.jar
ojdbc8.jar
-----------------------------------------------------------------------------------------------------
>>>src/main/resources
log4jdbc.log4j2.properties 파일 생성
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator 내용입력
-----------------------------------------------------------------------------------------------------
>>>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j

@Setter(onMethod_ = @Autowired)
@Select("select sysdate from dual")

@Service // 계층 구조상 주로 비즈니스 영역을 담당하는 객체임을 표시하기 위해 사용함.
@Log4j
@AllArgsConstructor // 생성자를 만들고 자동으로 주입함.

<BoardController.java>
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor

<BoardControllerTests.java>
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml",
               "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j

>>>Tomcat 더블클릭 - Modules - Edit - controller 부분 지우기
-----------------------------------------------------------------------------------------------------
P.173
>>>Oracle 테이블

사용자 추가 후

system/123456

db53/123456


create user db53 identified by 123456;
grant connect,resource to db53;
grant create view to db53;

create sequence seq_board;

create table tbl_board(
bno number(10,0),
title varchar2(200) not null,
content varchar2(2000) not null,
writer varchar2(50) not null,
regdate date default sysdate,
updatedate date default sysdate
);

create table tbl_member(
userid varchar2(50) not null primary key,
userpw varchar2(100) not null,
username varchar2(100) not null,
regdate date default sysdate,
updatedate date default sysdate,
enabled char(1) default '1');

create table tbl_member_auth(
userid varchar2(50) not null,
auth varchar2(50) not null,
constraint fk_member_auth foreign key(userid) references tbl_member(userid)
);

alter table tbl_board add constraint pk_board primary key(bno);

insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user00');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user01');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user02');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user03');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user04');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user05');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user06');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user07');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user08');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user09');
insert into tbl_board(bno,title,content,writer) values(seq_board.nextval,'테스트 제목','테스트 내용','user10');

-----------------------------------------------------------------------------------------------------
>>> BoardMapper.xml (@Select 어노테이션을 써도 되지만, 쿼리문이 복잡해질 경우 xml 파일로 관리하는게 유리하다 !!!)

src/main/resources -> com 폴더 -> jslhrd 폴더 -> mapper 폴더 생성 후, 그 안에 BoardMapper.xml 파일 생성
(com.jslhrd.mapper 패키지 명과 동일한 틀로)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
     PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jslhrd.mapper.BoardMapper">

     <!-- 부등호 들어가면 CDATA 처리 !!! -->

     <select id="getList" resultType="com.jslhrd.domain.BoardVO">
          <![CDATA[
               select * from tbl_board where bno > 0
          ]]>
     </select>

     <insert id="insert">
          insert into tbl_board(bno,title,content,writer) values(seq_board.nextval, #{title}, #{content}, #{writer})
     </insert>

     <insert id="insertSelectKey">
          <selectKey keyProperty="bno" order="BEFORE" resultType="long">
               select seq_board.nextval from dual
          </selectKey>
          insert into tbl_board(bno,title,content,writer) values(#{bno}, #{title}, #{content}, #{writer})
     </insert>

     <select id="read" resultType="com.jslhrd.domain.BoardVO">
          select * from tbl_board where bno = #{bno}
     </select>

     <update id="update">
          update tbl_board
          set title=#{title},
          content=#{content},
          writer=#{writer},
           updateDate=sysdate
          where bno=#{bno}
     </update>

     <delete id="delete">
          delete from tbl_board where bno=#{bno}
     </delete>

</mapper>

-----------------------------------------------------------------------------------------------------
src/main/resources -> com 폴더 -> jslhrd 폴더 -> mapper 폴더 생성 후, 그 안에 TimeMapper.xml 파일 생성
(com.jslhrd.mapper 패키지 명과 동일한 틀로)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
     PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jslhrd.mapper.TimeMapper">
     <select id="getTime2" resultType="string">
          select sysdate from dual
     </select>
</mapper>

-------------------------------------------------------------------------------------- src/main/resources -> com 폴더 -> jslhrd 폴더 -> mapper 폴더 생성 후, 그 안에 MemberMapper.xml 파일 생성
(com.jslhrd.mapper 패키지 명과 동일한 틀로)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
     PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jslhrd.mapper.MemberMapper">

     <resultMap type="com.jslhrd.domain.MemberVO" id="memberMap">
          <id property="userid" column="userid" />
          <result property="userid" column="userid" />
          <result property="userpw" column="userpw" />
          <result property="userName" column="username" />
          <result property="regDate" column="regdate" />
          <result property="updateDate" column="updatedate" />
          <collection property="authList" resultMap="authMap">
          </collection>
     </resultMap> <!-- 여러개의 데이터를 처리하는 경우 1:N의 결과를 처리할 수 있는 resultMap 태그 사용 -->

     <resultMap type="com.jslhrd.domain.AuthVO" id="authMap">
          <result property="userid" column="userid" />
          <result property="auth" column="auth" />
     </resultMap>

     <select id="read" resultMap="memberMap"> <!-- mem 테이블과 auth 테이블을 쪼인하여 userid에 해당하는 항목들을 가져옴. -->
          SELECT
               mem.userid, userpw, username, enabled, regdate, updatedate, auth
          FROM
               tbl_member mem LEFT OUTER JOIN tbl_member_auth auth on mem.userid = auth.userid where mem.userid = #{userid}
     </select>

</mapper>

--------------------------------------------------------------------------------------
>>> web.xml (인코딩 & Security)

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!-- The definition of the Root Spring Container shared by all Servlets and Filters --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml /WEB-INF/spring/security-context.xml <!-- security-context.xml을 로딩하도록 빈 설정 --> </param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 한글깨짐 현상 처리 시작 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 한글깨짐 현상 처리 끝 --> <!-- Spring Security 시작 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy </filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- Spring Security 끝 --> </web-app>
--------------------------------------------------------------------------------------
>>> Modal 코드

<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
     <div class="modal-dialog">
          <div class="modal-content">
               <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                    <h4 class="modal-title" id="myModalLabel">Modal title</h4>
               </div>
               <div class="modal-body">
(modal)
               </div>
               <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                    <button type="button" class="btn btn-primary">Save changes</button>
               </div>
          </div>
          <!-- /.modal-content -->
     </div>
     <!-- /.modal-dialog -->
</div>
<!-- /.modal -->


--------------------------------------------------------------------------------------
>>> security-context.xml
(WEB-INF / spring 폴더 내에 생성)
(Namespaces 에서 security 항목 체크 !!!)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 5.0버전은 에러가 발생 버그 때문에 지우기 --> <bean id="customAccessDenied" class="com.jslhrd.security.CustomAccessDeniedHandler"></bean> <bean id="customLoginSuccess" class="com.jslhrd.security.CustomLoginSuccessHandler"></bean> <!-- <bean id="customPasswordEncoder" class="com.jslhrd.security.CustomNoOpPasswordEncoder"></bean> --> <bean id="bcryptPasswordEndcoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean> <bean id="customUserDetailsService" class="com.jslhrd.security.CustomUserDetailsService"></bean> <security:http auto-config="true" use-expressions="true"> <security:intercept-url pattern="/sample/all" access="permitAll" /> <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')" /> <security:intercept-url pattern="/board/list" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/board/insert" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/board/update" access="hasRole('ROLE_MEMBER')" /> <!-- <security:access-denied-handler error-page="/accessError" /> error-page 나 ref 둘 중 하나만 !!! --> <security:access-denied-handler ref="customAccessDenied" /> <!-- <security:form-login /> --> <!-- 로그인창으로 강제 이동 시 해당하는 페이지 --> <security:form-login login-page="/customLogin" authentication-success-handler-ref="customLoginSuccess"/> </security:http> <security:authentication-manager> <security:authentication-provider user-service-ref="customUserDetailsService"> <!-- 최종적으로 customUserDetailsService 사용 --> <!--<security:jdbc-user-service data-source-ref="dataSource" users-by-username-query="select userid,userpw,enabled from tbl_member where userid=?" authorities-by-username-query="select userid,auth from tbl_auth where userid=?" /> change to Bcrypt <security:password-encoder ref="customPasswordEncoder" /> --> <security:password-encoder ref="bcryptPasswordEndcoder" /> </security:authentication-provider> <!-- ********************************************************************************************* <security:authentication-provider> <security:user-service> <security:user name="member" password="{noop}member" authorities="ROLE_MEMBER" /> <security:user name="admin" password="{noop}admin" authorities="ROLE_MEMBER, ROLE_ADMIN" /> </security:user-service> </security:authentication-provider> *********************************************************************************************** --> </security:authentication-manager> </beans> --------------------------------------------------------------------------------------
>>> servlet-context.xml
( Namespace의 security 추가 )
( 상단의 시큐리티 버전 5.0 숫자 지우고, global-method 태그 추가 )
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <!-- Enables the Spring MVC @Controller programming model --> <annotation-driven /> <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the /resources directory --> <resources mapping="/resources/**" location="/resources/" /> <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean> <context:component-scan base-package="com.jslhrd.controller" /> <security:global-method-security pre-post-annotations="enabled" secured-annotations="enabled" /> <!-- security 어노테이션을 사용하기 위한 태그 (Namespace의 security 추가 후, 상단의 5.0버전 숫자 제거) --> </beans:beans>


>>> STS 기초 세팅
Window -> Preference -> encoding 검색 -> Workspace,CSS,HTML,JSP,XML (UTF-8 변경)
Window -> Preference -> General -> Startup&Shutdown -> Workspaces (Prompt 체크, workspaces 선택)
Window -> Web Browser -> Chrome or Default
Servers -> Spring 기본 서버 삭제 -> New -> Tomcat v9.0 추가 후 경로 설정
Tomcat -> Overview (HTTP/1.1 -> 8087변경) -> Modules (Path -> / 변경)
main/wepapp/resources -> css,fonts,images,js 폴더 관리
Window -> Preferences -> XML -> XML Catalog -> UserSpecifiedEntries 클릭, Add 클릭,
     Location = http://mybatis.org/dtd/mybatis-3-mapper.dtd
     Key = -//mybatis.org//DTD Mapper 3.0//EN

-----------------------------------------------------------------------------------------------------------------------------

>>> hanulso/pom.xml 기초 세팅
java-version -> 1.8
springframework-version -> 5.0.7.RELEASE (@Get/PostMapping 어노테이션 사용 가능)
log4j-version -> 1.2.17

lombok-1.18.22 추가
HikariCP-2.7.4 추가 (커넥션 풀 처리)
mybatis(3.4.6), mybatis-spring(2.0.7), spring-txt, spring-jdbc 추가 (DB연동 처리)
commons-fileupload-1.3.3, commons-io-2.6 추가 (파일 첨부 처리)
thumbnailator-0.4.8 추가 (썸네일 이미지 생성시)
spring security 추가
jackson-databind-2.9.6 추가 (JOSN 포맷 변환)
jackson-dataformat-xml-2.9.6 추가 (xml 처리)
gson-2.8.2 추가 (테스트시 java instance를 json 타입의 문자열로 변환 처리)
javax.mail-1.6.2 추가 (메일 처리)
spring-context-support 추가 (캐쉬,메일,스케줄링,UI 등 부가적 기능)

junit-version -> 4.12
maven-compiler-plugin -version -> 3.5.1 & 1.8 & 1.8


https://mvnrepository.com/ <- dependency 검색
https://start.spring.io/ <- Spring Start Page

- 세팅이 반이다 !
- Test 중요 !
- build path (mysql-connector-java-8.0.28.jar , ojdbc8.jar)
- pom.xml 계속 수정

STS 세팅

    Window -> Preference -> encoding 검색 -> Workspace,CSS,HTML,JSP,XML (UTF-8 변경)
    Window -> Preference -> General -> Startup&Shutdown -> Workspaces (Prompt 체크, workspaces 선택)
    Window -> Web Browser -> Chrome or Default
    Servers -> Spring 기본 서버 삭제 -> New -> Tomcat v9.0 추가 후 경로 설정
    Tomcat -> Overview (HTTP/1.1 -> 8087변경) -> Modules (Path -> / 변경)
    main/wepapp/resources -> css,fonts,images,js 등의 폴더 관리
    Window -> Preferences -> XML -> XML Catalog -> UserSpecifiedEntries 클릭, Add 클릭,
      Location = http://mybatis.org/dtd/mybatis-3-mapper.dtd
      Key = -//mybatis.org//DTD Mapper 3.0//EN

MVC

   (Model View Controller)

   Presentation - Client의 요청을 받아 Business에게 처리를 전달.
   Business - 실제 요청에 대한 처리를 담당. (Service)
   Persistence - Business의 처리에 따라 데이터베이스에 접근. 저장,조회,삭제 등 수행.

Layered Architecture


   각 계층은 자신의 계층이 갖는 책임에만 충실하도록 개발해야 한다.

View

Model Model

Presentation
@Controller

Model Model

Business
@Service

DTO Model

Persistence

DTO Model

DB
(Oracle)



초기 설정/관리해야 할 .XML

p.608
    - pom.xml : 프로그램 추가(버전관리), spring5.0.7
    - web.xml : 한국어 지원 + 로그인 지원 + multipart-config
    - root-context.xml : DB 연결 지원
    - servlet-context.xml : spring security 어노테이션 설정 추가 + 첨부파일 upload 매핑 추가
    - security-context.xml : 로그인 관련( 인증 + 권한 )


동기 VS 비동기


    - 동기(Synchronized) : 프로그램이 작성된 순서대로 실행되는 것.
       반드시 앞의 동작이 끝나야 다음의 동작이 실행 됨.
    - 비동기(Asynchronized) : 타임라인의 분기를 나눠서 두개 이상의 함수가 동시에 동작하는 것.
    - Callback 함수 : 비동기로 함수를 실행할 경우라도 A -> B 동작 순서가 구분되어야 하는데,
       이런 경우 B를 callback함수로 A함수의 인자로 호출하면 온전하게 실행 가능.
    - promise : 무수히 중첩되는 복잡한 callback함수의 지옥을 대신하여 사용.
       비동기 작업이 종료된 후, 작업 실행의 성공,실패,결과값의 내용이 무엇인지 반환해주겠다고 약속해주는 객체.


mapper.xml


    전 프로젝트(jslhrd)에서는 src/main/resources 폴더에 mapper.xml 파일을 저장했지만,
    본 프로젝트(hanulso)에서는 mapper.xml 파일을 Mapper인터페이스가 있는 패키지와 같은 곳에 저장.
    xml 파일 dtd 환경설정 방법은 매뉴얼(STS 기초세팅) 참조.
    NoticeVO & DB(NOTICE 테이블) & Mapper.xml 의 각 변수명을 통일함에 주의.


(Oracle) Primary Key


    테이블 생성시 PK를 설정할 때, 컴럼 생성 문구 옆에 PRIMARY KEY를 입력하면,
    인덱스에 원치않는 이름의 pk가 생성되어 버린다.
    고로 컴럼 생성 문구를 다 적은 후, 마지막 줄에 constraint 제약조건 설정으로 PK 생성.
    CONSTRAINT 제약조건명 PRIMARY KEY (컬럼명)
    ex) constraint pk_notice PRIMARY KEY (bno)
    *테이블 생성 후, 따로 설정 가능.
    ex) ALTER TABLE 테이블명 ADD CONSTRAINT 제약조건명 PRIMARY KEY (컬럼명)


core / fmt 태그


    <c:forEach var="notice" items="${notice_list}" varStatus="status">
    status.index -> varStatus를 사용하여 반복문의 각 변수의 "0부터의 순서"를 나타냄.

    <c:set var="cnt" value="${notice_count}" />
    c:set -> core태그를 사용하여 변수명과 값을 지정해 새로운 변수 생성.

    <fmt:formatDate value="${regdate}" pattern="yyyy-MM-dd HH:ss" />
    formatDate -> fmt 태그를 사용하여 해당 변수를 원하는 패턴형식의 날짜로 출력.
    *value의 값이 String타입이면 오류가 발생. java.util.Date or java.sql.Date 타입 사용.


@RequestParam(" ")


    글 번호인 bno 값이 URL에서 String타입으로 들어올 때,
    @RequestParam("bno") int bno => 파라미터를 int로 받는다.

    ex)
    @GetMapping("/noticeview.do")
    public void noticeView(@RequestParam("bno") int bno, Model model) {
         model.addAttribute("nvo", service.noticeView(bno));
    }


다양한 SQL문 응용


    select count(*) from notice // 전체 레코드의 개수.
    select max(bno) from notice // 가장 큰 bno의 값.
    select min(bno) from notice // 가장 작은 bno의 값.
    select * from notice where bno = ( select min(bno) from notice where bno > #{bno} )
    select * from (select * from notice order by bno asc) where bno > #{bno} and rownum=1
    // 다음 bno의 값 (현재 bno의 값보다 큰 값 중에서 가장 작은 값)에 해당하는 NoticeVO


Oracle Hint / Index


    Hint => Optimizer에게 데이터 스캔 경로, 조인 방법 등을 알려주기 위해 SQL 구문에 작성하는 것.
       /*+ */ 안에 감싸서 사용한다. ex) /*+ INDEX_ASC(table_name index_name) */ 인덱스로 내림차순 정렬

    pageNum(현재 페이지)과 amount(페이지당 게시물 수)가 넘어왔을 때의 "게시판 리스트" SQL문.
       select bno,title,content,writer,regdate,hits from (
       select /*+ index_desc(notice notice_pk)*/ rownum rn,bno,title,content,writer,regdate,hits from notice
       where rownum <= #{pageNum} * #{amount} ) where rn > (#{pageNum}-1) * #{amount}

    주의 - rownum은 조건에 따라 계속 바뀌기 때문에 아래처럼 실행하면 아무 결과도 나오지 않음.
       select /*+ index_desc(notice notice_pk) */ rownum rn, bno, title from notice
       where rownum > 10 and rownum <=20


@Data 어노테이션


    스프링 프레임워크에서는 PersonVO라는 객체에 String 타입의 name, age 맴버변수가 있을 때,
    URL로 넘어온 name, age 파라미터가 자동으로 Controller의 메서드의 매개변수(PersonVO)로 바인딩 된다.


JQuery


    var action = $("#actionForm") // actionForm라는 ID속성을 가진 폼을 변수로 저장.
    $(".paging > a").on("click", function(e){ // .paging라는 Class속성 중 a태그를 "click"하면 함수 실행.
    // .paging a => paging 클래스 안의 a 태그 // .paging > a => paging 클래스 바로 밑에 있는 a 태그
    e.preventDefault(); // a태그를 클릭해도 href링크 이동, 혹은 form의 submit 등을 새로 실행하지 않도록 함.
    action.find("input[name='pageNum']").val($(this).attr("href"));
    // action(폼)의 name이 'pageNum'인 input을 찾아 값에 '클릭한 a태그의 href속성 값'을 대입

    ex) 실제 코드

          $(function() {
               var action = $("#actionForm")
               $(".paging > a").on("click", function(e){
                    e.preventDefault();
                    action.find("input[name='pageNum']").val($(this).attr("href"));
                    action.submit();
               });
          });


mybatis 반복 실행문 태그 (sql)


    1. 검색 조건을 다루는 객체 CriteriaVO에 getTypeArr 메서드 추가.

      public String[] getTypeArr() {
          return type == null ? new String[] {} : type.split("");
      }

    2. mapper.xml에 sql태그 추가.

<sql id="criteria">
   <trim prefix="(" suffix=") and " prefixOverrides="OR">
      <foreach collection="typeArr" item="type">
         <trim prefix="OR">
            <choose>
               <when test="type == 'T'.toString()">
                  title like '%'||#{keyword}||'%'
               </when>
               <when test="type == 'C'.toString()">
                  content like '%'||#{keyword}||'%'
               </when>
               <when test="type == 'W'.toString()">
                  writer like '%'||#{keyword}||'%'
               </when>
            </choose>
         </trim>
      </foreach>
   </trim>
</sql>

    3. notice.jsp에 검색 옵션 Form 구현.

<select name="type" class="select">
   <option value=""> 선택 </option>
   <option value="T"> 제목 </option>
   <option value="C"> 내용 </option>
   <option value="W"> 글쓴이 </option>
   <option value="TC"> 제목 + 내용 </option>
</select>
<input type="text" name="keyword" class="search_word">
<input type="hidden" name="pageNum" value="1">
<input type="hidden" name="amount" value="${pVO.cri.amount}">


Spring 첨부파일 처리하기


    1. 첨부파일은 그냥 Post방식으로 보내도 VO객체에 바인딩되지 않음.
    Controller에서 @RequestParam("변수명") MultipartFile "변수명" 으로 받아줘야 함.
    Form에 enctype="multipart/form-data" 속성 추가해줘야 함.

    2. C://upload 파일 하나 생성해두기.

    3. web.xml의 servlet 태그 사이 중 맨 밑쪽에 코드 추가.

     <multipart-config>
          <location>c:\\upload</location> <!-- c드라이브에 upload 임시 파일 하나 만들기 -->
               <max-file-size>20971520</max-file-size>
               <max-request-size>41943040</max-request-size>
          <file-size-threshold>20971520</file-size-threshold>
     </multipart-config>

    4. servlet-context.xml에 mapping 추가하기.

     <resources mapping="/upload/**" location="file:///c:/upload/" />

    5. ex) PortfolioController 중 첨부파일 처리하는 메서드.

     //날짜 폴더 만들기
     public String getFolder() {
          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
          Date date = new Date();
          String str = sdf.format(date);
          return str.replace("-", File.separator);
     }

     @PostMapping("portfolio_write.do")
     public String Portfolio_write(PortfolioVO pVO, @RequestParam ("uploadfile") MultipartFile uploadfile ){
          log.info("Title : " + pVO.getTitle());
          log.info("Title : " + pVO.getContent());
          log.info("File Name : " + uploadfile.getOriginalFilename());
          log.info("File Size : " + uploadfile.getSize());
          //explorer 브라우저 같은 경우는 경로까지 파일 이름에 포함되기 때문에, 마지막 \\ 뒤의 있는 문자열만 잘라냄.
          String uploadFileName = uploadfile.getOriginalFilename();
          uploadFileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\")+1);
          log.info("uploadFileName : " + uploadFileName);
          //중복파일 처리
          UUID uuid = UUID.randomUUID();
          uploadFileName = uuid.toString() + "_" + uploadFileName;
          log.info("UUID File Name : " + uploadFileName);
          //업로드 패스 구하기
          String uploadFolder = "c:\\upload";
          File uploadPath = new File(uploadFolder, getFolder()); // c:/upload/2022/06/02
          log.info("uploadPath : " + uploadPath);
          //날짜 폴더 만들기
          if(uploadPath.exists()==false) {
               uploadPath.mkdirs();
          }
          //업로드하는 경로와 파일 객체 생성
          File uploadSaveFile = new File(uploadPath, uploadFileName); // c:/upload/2022/06/02/image.jpg
          log.info("uploadSaveFile : " + uploadSaveFile);
          // 년/월/일/파일이름만 구하기.
          String uploadURL = uploadSaveFile.toString().substring(10); // 2022/06/02/image.jpg
          log.info("REAL UPLOAD URL : " + uploadURL);
          // 첨부파일 업로드 폴더에 저장하기
          try {
               uploadfile.transferTo(uploadSaveFile); // 파일 실제로 전송
               pVO.setUploadfile(uploadURL); // 테이블에 저장하기 위한 경로
          } catch (Exception e) {
               e.printStackTrace();
          }

          return "redirect:/portfolio/portfolio.do";
     }



프로젝트 (포트폴리오) 계획


    1. 주제
    2. 개발 목적
    3. ERD맵 (테이블 설계)
    4. 화면 구현
    5. Tree Map (각 화면별 필요한 기능 정리)


Gyoon