
// 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 { }
@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를 생성하는 작업이 필요합니다! 아래는 그를 위한 작업입니다!
<?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 이용)
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 처리를 객체와 매핑해서 처리연동 방식
- MyBatis는 단독으로 실행이 가능한 프레임워크지만 mybatis-spring 라이브러리를 이용하면 쉽게 통합해서 사용가능
- 과거에는 주로 별도의 DAO를 구성하고 여기서 MyBatis의 sqlSession을 이용하는 방식을 주로 사용했찌만, 최근에는 MyBatis는 인터페이스를 이용하고 실제 코드는 자동으로 생성하는 방식을 사용 (위에서 말한 Mapper인터페이스와 XML을 이용하는 방식)
관련 라이브러리 갖고오기
Spring JDBC
Spring Transaction
MyBatis
MyBatis Spring
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>
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냐에 따라 다르게 작성합니다.
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
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" />request.getRequestDispatcher() 또는 response.sendRedirect() 로 view를 지정해주지 않아도 Spring이 알아서💡 잠깐?! 어떤건 src > resources 폴더에 넣고! 어떤건 WEB-INF 폴더에 넣나요?
WEB-INF 폴더의 경우 사용자 측에서 접근이 불가하고, 컨테이너를 이용해서만 접근이 가능한 특징이 있습니다. 따라서 보안적으로 접근 제한이 필요한 설정 파일의 경우 대부분 WEB-INF디렉토리를 이용합니다.
다만, WEB-INF 폴더의 경우 서블릿 생성 시점에 갖고오는 설정정보들을 넣어주고 다른 디렉토리의 경우 필요시 마다 접근해서 갖고오는 성격의 설정정보들로 구성하는게 일반적이다.
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 맛보기하자!
// 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("---------------------");
}
}
<!-- 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>
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>