[TIL] 240416

Geehyun(장지현)·2024년 4월 16일

TIL

목록 보기
62/70
post-thumbnail

Today

  • DAO를 interface로 바꾸기 실습 (결합도를 약하게 하기 위해)
// SampleDAO
package org.fullstack4.springmvc.sample;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Repository;

public interface SampleDAO {
}
// SampleDAO 구현체 1
package org.fullstack4.springmvc.sample;

import org.springframework.stereotype.Repository;

@Repository
public class SampleDAOImpl implements SampleDAO {
}
// SampleDAO 구현체 2
package org.fullstack4.springmvc.sample;

import org.springframework.stereotype.Repository;

@Repository
public class EventSampleDAOImpl implements SampleDAO{
}

=> 이런식으로 DAO의 구현체를 2개 만들고 Service에서 해당 DAO를 의존성 주입하게 되면(RequiredArgsConstructor 사용) spring에서 어떤거를 써야할지 모르겠다는 에러가 뜸

10:21:35  WARN [org.springframework.context.support.GenericApplicationContext] Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sampleService' defined in file [D:\JAVA4\spring\springweb\springmvc\out\production\classes\org\fullstack4\springmvc\sample\SampleService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.fullstack4.springmvc.sample.SampleDAO' available: expected single matching bean but found 2: eventSampleDAOImpl,sampleDAOImpl

💡 Bean끼리 충돌(?)할 경우!
1. @Qualifier("사용자 정의 이름") 어노테이션을 사용해서, Bean에 이름을 주고, 의존성 주입하는 부분에서 사용할 Bean 이름을 지정해주면 됨!!

// Service
package org.fullstack4.springmvc.sample;

import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
@ToString
@RequiredArgsConstructor
public class SampleService {
    //@Autowired
    @Qualifier("event")
    private final SampleDAO sampleDAO;
}
// SapleDAO의 구현체 중 하나
package org.fullstack4.springmvc.sample;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

@Repository
@Qualifier("event")
public class EventSampleDAOImpl implements SampleDAO {
}
  1. @Primary 어노테이션을 이용하여 기본으로 실행 될 Bean 지정해주기
// SapleDAO의 구현체 중 하나
package org.fullstack4.springmvc.sample;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

@Repository
@Primary
public class SampleDAOImpl implements SampleDAO {
}

이러한 유연한 의존성 주입을 느스한 결합 이라고함

  • SpringMVC 준비하기
    ApplicationContext 내에서 Bean들이 생성되어 관리되는 구조이며, 이러한 ApplicationContext가 웹에서 동작하려면, 웹애플리케이션이 실행될 때 spring을 로드해서 웹 애플리케이션 내부에 spring의 ApplicationCont-text를 생성하는 작업이 필요합니다! 아래는 그를 위한 작업입니다!

    1. Spring Web MVC 라이브러리 가져오기
    2. web.xml에 관련 설정 추가
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/root-context.xml</param-value>
        </context-param>
        <!--이 웹서비스를 시작할때 root-context.xml을 불러와라!-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <!--spring에게 처리를 위임하겠다라는 의미-->
    </web-app>
  • DB 연결(HikariCP 이용)

    1. 관련 라이브러리 가져오기
      MariaDB Java Client
      HikariCP
    2. root-context.xml에 Bean으로 설정해주기
    <?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:context = "http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context
                               http://www.springframework.org/schema/context/spring-context.xsd">
    <!--       1. `root-context.xml` 처럼 xml을 이용해서 의존성 주입하는 방법 -->
    <!--       <bean class="org.fullstack4.springmvc.sample.SampleDAO" />-->
    <!--       <bean class="org.fullstack4.springmvc.sample.SampleService" />-->
    
    <!--       2. `<context:component-scan>`을 이용하는 방법 -->
           <context:component-scan base-package="org.fullstack4.springmvc.sample" />
           <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
                  <property name="driverClassName" value="org.mariadb.jdbc.Driver"/>
                  <property name="jdbcUrl" value="jdbc:mariadb://localhost:3306/webdb"/>
                  <property name="username" value="user"/>
                  <property name="password" value="1234"/>
                  <property name="dataSourceProperties">
                         <props>
                                <prop key="cachePrepStmts">true</prop>
                                <prop key="prepStmtCacheSize">250</prop>
                                <prop key="prepStmtCacheSqlLimit">2048</prop>
                         </props>
                  </property>
           </bean>
           <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
                  <constructor-arg ref="hikariConfig" />
           </bean>
           <!--위 내용은 즉 HikariConfig hikariConfig = new HikariConfig() 와 동일한 내용-->
    </beans>
  • MyBatis와 Spring

    객체 지향 언어인 자바의 관계형 데이터베이스 프로그래밍을 좀 더 쉽게 할 수 있게 도와 주는 개발 프레임 워크

    • sql Mapping Framework - SQL 처리를 객체와 매핑해서 처리
      => SQL을 별도의 XML에 작성하여 Mapper를 이용하여 매핑
    • JDBC를 이용해서 PreparedStatement / ResultSet에 대한 객체 처리를 자동으로 수행
    • Connection 등의 JDBC자원들에 대한 자동 close()처리

      연동 방식

      • MyBatis는 단독으로 실행이 가능한 프레임워크지만 mybatis-spring 라이브러리를 이용하면 쉽게 통합해서 사용가능
      • 과거에는 주로 별도의 DAO를 구성하고 여기서 MyBatis의 sqlSession을 이용하는 방식을 주로 사용했찌만, 최근에는 MyBatis는 인터페이스를 이용하고 실제 코드는 자동으로 생성하는 방식을 사용 (위에서 말한 Mapper인터페이스와 XML을 이용하는 방식)
    • 연동하자!
    1. 관련 라이브러리 갖고오기
      Spring JDBC
      Spring Transaction
      MyBatis
      MyBatis Spring

    2. root-context.xml에 MyBatis Bean 추가

      <?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:context = "http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                 http://www.springframework.org/schema/beans/spring-beans.xsd
                                 http://www.springframework.org/schema/context
                                 http://www.springframework.org/schema/context/spring-context.xsd">
      <!--       1. `root-context.xml` 처럼 xml을 이용해서 의존성 주입하는 방법 -->
      <!--       <bean class="org.fullstack4.springmvc.sample.SampleDAO" />-->
      <!--       <bean class="org.fullstack4.springmvc.sample.SampleService" />-->
      
      <!--       2. `<context:component-scan>`을 이용하는 방법 -->
             <context:component-scan base-package="org.fullstack4.springmvc.sample" />
             <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
                    <property name="driverClassName" value="org.mariadb.jdbc.Driver"/>
                    <property name="jdbcUrl" value="jdbc:mariadb://localhost:3306/webdb"/>
                    <property name="username" value="user"/>
                    <property name="password" value="1234"/>
                    <property name="dataSourceProperties">
                           <props>
                                  <prop key="cachePrepStmts">true</prop>
                                  <prop key="prepStmtCacheSize">250</prop>
                                  <prop key="prepStmtCacheSqlLimit">2048</prop>
                           </props>
                    </property>
             </bean>
             <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
                    <constructor-arg ref="hikariConfig" />
             </bean>
             <!--위 내용은 즉 HikariConfig hikariConfig = new HikariConfig() 와 동일한 내용-->
      
             <!-- MyBatis Bean 설정 -->
             <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
                    <property name="dataSource" ref="dataSource" />
             </bean>
      </beans>
    • 사용해보기
    1. Mapper 인터페이스 만들어서 sql어노테이션 사용해보기

      package org.fullstack4.springmvc.mapper;
      
      import org.apache.ibatis.annotations.Select;
      
      public interface TimeMapper {
          @Select("Select now()")
          String getTime();
      }

      위 @Select 어노테이션 부분은 작성할 쿼리가 SELECT/INSERT/DELETE냐에 따라 다르게 작성합니다.

    2. xml로 Mapping해보기
      Mapper 인터페이스에서 추상메서드만 정의해놓고 body는 xml에서 구현하는 방식

      // Mapper 인터페이스
      package org.fullstack4.springmvc.mapper;
      
      public interface TimeMapper2 {
          String getTime2();
      }
      <!-- Mapper xml => src > main > resource 폴더 하위로 작성-->
      <?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="org.fullstack4.springmvc.mapper.TimeMapper2">
          <select id="getTime2" resultType="String">
              SELECT NOW()
          </select>
      </mapper>

      위 select xml 태그 부분은 작성할 쿼리가 SELECT/INSERT/DELETE냐에 따라 다르게 작성합니다.
      select xml 태그의 id는 Mapper 인터페이스의 메서드명과 일치해야합니다.

      <?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:context="http://www.springframework.org/schema/context"
             xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                 http://www.springframework.org/schema/beans/spring-beans.xsd
                                 http://www.springframework.org/schema/context
                                 http://www.springframework.org/schema/context/spring-context.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
      <!--       1. `root-context.xml` 처럼 xml을 이용해서 의존성 주입하는 방법 -->
      <!--       <bean class="org.fullstack4.springmvc.sample.SampleDAO" />-->
      <!--       <bean class="org.fullstack4.springmvc.sample.SampleService" />-->
      
      <!--       2. `<context:component-scan>`을 이용하는 방법 -->
             <context:component-scan base-package="org.fullstack4.springmvc.sample" />
             <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
                    <property name="driverClassName" value="org.mariadb.jdbc.Driver"/>
                    <property name="jdbcUrl" value="jdbc:mariadb://localhost:3306/webdb"/>
                    <property name="username" value="user"/>
                    <property name="password" value="1234"/>
                    <property name="dataSourceProperties">
                           <props>
                                  <prop key="cachePrepStmts">true</prop>
                                  <prop key="prepStmtCacheSize">250</prop>
                                  <prop key="prepStmtCacheSqlLimit">2048</prop>
                           </props>
                    </property>
             </bean>
             <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
                    <constructor-arg ref="hikariConfig" />
             </bean>
             <!--위 내용은 즉 HikariConfig hikariConfig = new HikariConfig() 와 동일한 내용-->
      
             <!-- MyBatis Bean 설정 -->
             <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
                    <property name="dataSource" ref="dataSource" />
                    <property name="mapperLocations" value="classpath:/mappers/**/*.xml" />
             </bean>
             <!-- 위 Bean 설정에서  mapperLocations으로 Mppper.xml 설정 함. -->
             <mybatis:scan base-package="org.fullstack4.springmvc.mapper"/>
             <!-- MyBatis Bean 설정 -->
      </beans>

      위 열심히 만든 Mapper.xml 연결해주는 부분! (<property name="mapperLocations" value="classpath:/mappers/**/*.xml" />)

MyBatis는 다중 쿼리는 못 날린다!
MyBatis로 쿼리를 사용할 때는

SELECT NOW() AS r1
SELECT NOW() AS r2

이런식으로 한번에 여러개의 쿼리를 한번에 못날린다는 특징이 있다.

  • Spring web MVC

    • Front-Controller 패턴을 이용해서 모든 흐름의 사전/사후 처리를 가능하도록 설계되었다
    • 어노테이션을 적극적으로 활용해서 최소한의 코드로 많은 처리가 가능하도록 설계되었다.
    • HttpServletRequest/HttpServletResponse를 이용하지 않아도 될 만큼 추상화된 방식응로 개발 가능
    • 관련설정 추가해보자!
      1. 프로젝트 내 servlet-context.xml 파일 생성
        => 기존 root-context.xml에 넣어도 되지만, 일반적으로 3-Tier를 구분하듯이 다루는 영역이 web영역이다 보니 별도의 설정파일로 빼는 것이 일반적
        <?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:mvc="http://www.springframework.org/schema/mvc"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                 http://www.springframework.org/schema/beans/spring-beans.xsd
                                 http://www.springframework.org/schema/mvc
                                 https://www.springframework.org/schema/mvc/spring-mvc.xsd">
          <mvc:annotation-driven/>
          <mvc:resources mapping="/resources/**" location="/resources/"/>
          <!--  webapp(루트) 폴더 밑으로 위 설정한 "resources" 디렉토리를 만들어줘야함.  -->
          <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
              <property name="prefix" value="/WEB-INF/views/" />
              <property name="suffix" value=".jsp" />
          </bean>
          <!--
            <property name="prefix" value="/WEB-INF/views/" /> => 접두어로 "/WEB-INF/views/" 이걸 알아서 넣어서 읽어오라는 설정
            <property name="suffix" value=".jsp" /> => 접미사로 ".jsp"로 끝나는 파일로 알아서 읽어와라!
            -->
        </beans>
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
        로 설정할 경우 Controller 내에서 따로 request.getRequestDispatcher() 또는 response.sendRedirect() 로 view를 지정해주지 않아도 Spring이 알아서
        설정한 prefix + Servlet-url + suffix 를 조합해서 view를 찾아감...!

        💡 잠깐?! 어떤건 src > resources 폴더에 넣고! 어떤건 WEB-INF 폴더에 넣나요?
        WEB-INF 폴더의 경우 사용자 측에서 접근이 불가하고, 컨테이너를 이용해서만 접근이 가능한 특징이 있습니다. 따라서 보안적으로 접근 제한이 필요한 설정 파일의 경우 대부분 WEB-INF디렉토리를 이용합니다.
        다만, WEB-INF 폴더의 경우 서블릿 생성 시점에 갖고오는 설정정보들을 넣어주고 다른 디렉토리의 경우 필요시 마다 접근해서 갖고오는 성격의 설정정보들로 구성하는게 일반적이다.

    1. web.xml 내 설정추가
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/root-context.xml</param-value>
        </context-param>
        <!--이 웹서비스를 시작할때 root-context.xml을 불러와라!-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <!--spring에게 처리를 위임하겠다라는 의미-->
    
        <!-- web.xml에 설정되는 Servlet관련 설정을 spring을 이용해서 설정하게끔 하는 내용 -->
        <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/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>
        <!--  <load-on-startup>1</load-on-startup> 서버가 시작할 때 한번 자동으로 실행해랏!  -->
    </web-app>
  • 실습 : 열심히 설정한 프로젝트에 SampleController를 만들어서 Spring web MVC 맛보기하자!

  1. 간단하게 Controller를 만들어보자
// controller > SampleController
package org.fullstack4.springmvc.controller;

import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Log4j2
@Controller
public class SampleController {
    @GetMapping("/hello")
    public void hello() {
        log.info("---------------------");
        log.info("hello");
        log.info("---------------------");
    }
}
  1. Controller에서 JSP를(view)를 작성해보자
<!-- WEB-INF > views > hello.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ page trimDirectiveWhitespaces="true" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>Hello JSP</h1>
<p>
    안녕 전 아무도 호출하지 않았지만 spring이 알아서 갖고온 jsp페이지입니다
</p>
</body>
</html>
  1. 만든 컨트롤러를 spring이 알아서 갖고올 수 있게 servlet.xml 파일에 설정해주자!
<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/mvc
                           https://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/context
                           https://www.springframework.org/schema/context/spring-context.xsd">
    <mvc:annotation-driven/>
    <mvc:resources mapping="/resources/**" location="/resources/"/>
    <!--  webapp(루트) 폴더 밑으로 위 설정한 "resources" 디렉토리를 만들어줘야함. : 정적인 소스(html, css, js, 이미지 등)을 서비스 하기 위한 경로  -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views" />
        <property name="suffix" value=".jsp" />
    </bean>
    <!--
      <property name="prefix" value="/WEB-INF/views" /> => 접두어로 "/WEB-INF/views/" 이걸 알아서 넣어서 읽어오라는 설정
      <property name="suffix" value=".jsp" /> => 접미사로 ".jsp"로 끝나는 파일로 알아서 읽어와라!
      또한 request.getRequestDispatcher("").forward() 에서 입력한 url에 대해서도 위 prefix/suffix 정보를 체크 함.
      -->
    
    <!-- Servelt Controller관련 경로 설정 -->
    <context:component-scan base-package="org.fullstack4.springmvc.controller" />
</beans>

Review

  • spring은 설정이 반이라고는 많이 들었는데, 정말 오래걸렸다....
    뭔가 알아서 척척해주니 편한것같기는한데...아직은 익숙하지 않아서 얼른 익숙해져야겠다!
  • WEB-INF 내부의 경우 직접 url로 호출이 불가하기 때문에 response.sendRedirect()로도 들어갈 수 없다!!!
  • 정처기 공부 계획
    4/15 ~ 4/21 : 시나공 기본서 전부 보기
    4/22 ~ 4/26 : 기출문제집만 열심히 풀기 (시나공 기출문제집 + 수제비 final 실기 모의고사)
profile
블로그 이전 했습니다. 아래 블로그 아이콘(🏠) 눌러서 놀러오세요

0개의 댓글