테스트 환경
Tomcat 9
Java 17
워크스페이스 C:\www
C:\www> mvn archetype:generate -Dfilter=maven-archetype-webapp Choose archetype: 1: remote -> com.haoxuer.maven.archetype:maven-archetype-webapp 2: remote -> com.lodsve:lodsve-maven-archetype-webapp 3: remote -> org.apache.maven.archetypes:maven-archetype-webapp 4: remote -> org.bytesizebook.com.guide.boot:maven-archetype-webapp Choose a number or apply filter: : 3 Choose org.apache.maven.archetypes:maven-archetype-webapp version: 1: 1.0-alpha-1 2: 1.0-alpha-2 3: 1.0-alpha-3 4: 1.0-alpha-4 5: 1.0 6: 1.3 7: 1.4 Choose a number: 7: ↵ Define value for property 'groupId': net.java_school Define value for property 'artifactId': mybatisspring Define value for property 'version' 1.0-SNAPSHOT: : ↵ Define value for property 'package' net.java_school: : ↵ Confirm properties configuration: groupId: net.java_school artifactId: mybatisspring version: 1.0-SNAPSHOT package: net.java_school Y: : ↵
빌드가 완료되면 C:\www에 mybatisspring라는 폴더가 생긴다. C:\www\mybatisspring이 프로젝트 루트 디렉터리이다.
Spring MVC 테스트
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.java_school</groupId> <artifactId>mybatisspring</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>mybatisspring Maven Webapp</name> <url>http://localhhost:8080</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <spring.version>5.3.33</spring.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <finalName>mybatisspring</finalName> <pluginManagement> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> <configuration> <filesets> <fileset> <directory>src/main/webapp/WEB-INF/classes</directory> </fileset> <fileset> <directory>src/main/webapp/WEB-INF/lib</directory> </fileset> </filesets> </configuration> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
src/main/webapp/WEB-INF/web.xml
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"> <display-name>Mybatis Spring</display-name> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>mybatisspring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/app-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mybatisspring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
src/main/webapp/WEB-INF/app-config.xml
app-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:resources mapping="/resources/**" location="/resources/" /> <mvc:annotation-driven /> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
홈페이지
src/main/webapp/resources/css/styles.css
styles.css
@CHARSET "UTF-8"; @import url(http://fonts.googleapis.com/earlyaccess/notosanskr.css); html, body { margin: 0; padding: 0; background-color: #FFF; font-family: "Noto Sans KR", "Liberation Sans", Helvetica, "돋움", dotum, sans-serif; } #wordcard { margin: 7px auto; padding: 1em; border: 3px solid grey; width: 600px; text-align: center; }
src/main/webapp/WEB-INF/views/index.jsp
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <title>Spring MVC Test</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="Keywords" content="Spring MVC Test" /> <meta name="Description" content="This is web app for Spring MVC Test" /> <link href="./resources/css/styles.css" rel="stylesheet" /> </head> <body> <div id="wordcard"> <h1>Vocabulary</h1> <p> 어휘 </p> </div> </body> </html>
영어 단어와 그 의미를 보여주는 화면이다.
화면이 나오면, 데이터베이스 설계를 시작으로, 자바 빈즈, DAO, 서비스, 컨트롤러 순으로 구현하는 게 일반적이지만, 대부분을 생략하고 컨트롤러와 뷰만으로 동작하는 예제를 만들어 보자.
컨트롤러
src/main/java/net/java_school/english/HomeController.java
package net.java_school.english; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class HomeController { @GetMapping("/") public String homepage() { return "index"; } }
src/main/webapp/index.jsp 파일 삭제
컨트롤러를 통해 홈페이지("/")가 서비스되도록 하려면 홈페이지 경로로 매핑된 다른 서블릿이 없어야 한다.
src/main/webapp/index.jsp 파일을 삭제한다.
스프링 설정 파일에 컨트롤러 추가
<bean id="homeController" class="net.java_school.english.HomeController" />
ROOT 애플리케이션 변경
톰캣을 중지하고 ROOT.xml 파일을 아래처럼 작성한 후 {Tomcat}\Catalina\localhost 디렉터리 복사한다. --{Tomcat}은 톰캣이 설치된 디렉터리를 의미--
ROOT.xml
<?xml version="1.0" encoding="UTF-8"?> <Context docBase="C:/www/mybatisspring/src/main/webapp" reloadable="true"> </Context>
WEB-INF 바로 위 디렉터리가 도큐먼트베이스다.
빌드와 배치
루트 디렉터리에서 다음을 실행한다.
mvn compile war:inplace
war:inplace 옵션은 src/main/WEB-INF/classes에 바이트코드를, src/main/WEB-INF/lib에 의존 라이브러리를 생성시킨다.
테스트
톰캣을 시작한다.
http://localhost:8080을 요청한다.
컨트롤러가 어떻게 동작하는지 확인했다.
다음은 컨트롤러가 뷰에 데이터를 어떻게 세팅하는지 알아보자.
예제를 간단하게 유지하기 위해 데이터베이스는 사용하지 않겠다.
자바 빈즈
src/main/java/net/java_school/english/WordCard.java
WordCard.java
package net.java_school.english; public class WordCard { private String word; private String definitions; public WordCard() {} public WordCard(String word, String definitions) { this.word = word; this.definitions = definitions; } public String getWord() { return word; } public void setWord(String word) { this.word = word; } public String getDefinitions() { return definitions; } public void setDefinitions(String definitions) { this.definitions = definitions; } }
DAO
src/main/java/net/java_school/english/WordCardDao.java
WordCardDao.java
package net.java_school.english; public class WordCardDao { private final String[][] words = { {"illegible","읽기 어려운"}, {"vulnerable","취약한"}, {"abate","감소시키다"}, {"clandestine","은밀한"}, {"sojourn","잠시 머무름, 체류"}, {"defiance","도전, 저항, 반항"}, {"infraction","위반"} }; public WordCard selectOne() { int no = (int)(Math.random() * 7) + 1; WordCard wordCard = new WordCard(); wordCard.setWord(words[no - 1][0]); wordCard.setDefinitions(words[no - 1][1]); return wordCard; } }
<bean id="wordCardDao" class="net.java_school.english.WordCardDao" />
서비스
src/main/java/net/java_school/english/WordCardService.java
WordCardService.java
package net.java_school.english; public class WordCardService { private WordCardDao wordCardDao; public WordCardService(WordCardDao wordCardDao) { this.wordCardDao = wordCardDao; } public WordCard getWordCard() { return wordCardDao.selectOne(); } }
<bean id="wordCardService" class="net.java_school.english.WordCardService"> <constructor-arg ref="wordCardDao" /> </bean>
컨트롤러 수정
package net.java_school.english; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; public class HomeController { private WordCardService wordCardService; public HomeController(WordCardService wordCardService) { this.wordCardService = wordCardService; } @GetMapping("/") public String homepage(Model model) { WordCard wordCard = wordCardService.getWordCard(); model.addAttribute("wordCard", wordCard); return "index"; } }
<bean id="homeController" class="net.java_school.english.HomeController"> <constructor-arg ref="wordCardService" /> </bean>
뷰 수정
src/main/webapp/WEB-INF/views/index.jsp
body 엘리먼트 내용을 아래처럼 수정한다.
<div id="wordcard"> <h1>${wordCard.word }</h1> <p> ${wordCard.definitions } </p> </div>
테스트
mvn clean compile war:inplace
http://localhost:8080
데이터베이스 다루기
MyBatis-Spring을 사용하여 앱이 데이터베이스를 사용하도록 수정해 보자.
MyBatis-Spring은 스프링에서 MyBatis를 편리하게 사용하기 위한 연동 모듈이다.
MyBatis는 SQL 매핑 퍼시스턴스 프레임워크이다.
https://mybatis.org/spring/ko/getting-started.html
아래 내용은 공식 사이트 시작하기와 순서를 같게 했다.
홈페이지 디자인 수정
새 단어를 추가하기 위한 폼을 #wordcard 다음에 추가한다.
<form id="new-form" method="post"> <input type="text" name="word" /> <input type="text" name="definitions" /> <input type="submit" value="Add" style="color: grey;" /> </form>
다음 스타일을 추가한다.
src/main/webapp/resources/css/styles.css
#new-form { margin: 7px auto; padding-left: 2em; width: 600px; text-align: right; font-size: 0.8em; }
scott 계정에 접속해 다음 테이블을 생성한다.
create table wordcard ( word varchar2(45), definitions varchar2(4000), constraint pk_wordcard primary key(word) );
mybatis-spring과 함께 스프링 JDBC, 오라클 JDBC 드라이버, 아파치 DBCP 그리고 MyBatis 라이브러리를 함께 추가한다.
pom.xml
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc6 --> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.4</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.11</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.0</version> </dependency>
commons-dbcp는 아파치에서 제공하는 데이터베이스 커넥션 풀이다.
ojdbc6.jar는 JDBC 4 드라이버이므로 DBCP 1.4 버전을 추가했다.
참고: https://dlcdn.apache.org//commons/dbcp/
스프링 설정 파일에 데이터소스와 SqlSessionFactoryBean을 추가한다.
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" /> <property name="username" value="scott" /> <property name="password" value="tiger" /> <property name="maxActive" value="100" /> <property name="maxWait" value="1000" /> <property name="poolPreparedStatements" value="true" /> <property name="defaultAutoCommit" value="true" /> <property name="validationQuery" value=" SELECT 1 FROM DUAL" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>
commons-dbcp2를 사용한다면, maxActive와 maxWait 파라미터는 maxTotal과 maxWaitMillis로 바꿔야 한다.
자바 빈즈
src/main/java/net/java_school/english/WordCard.java
WordCard.java
package net.java_school.english; public class WordCard { private String word; private String definitions; public WordCard() {} public WordCard(String word, String definitions) { this.word = word; this.definitions = definitions; } public int getWord() { return word; } public void setWord(String word) { this.word = word; } public String getDefinitions() { return definitions; } public void setDefinitions(String definitions) { this.definitions = definitions; } }
매퍼 인터페이스
매퍼 인터페이스를 다른 스프링 컴포넌트와 구별되는 패키지로 만드는 게 좋다.
src/main/java/net/java_school/mybatis/WordMapper.java
WordMapper.java
package net.java_school.mybatis; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; public interface WordMapper { @Insert("INSERT INTO wordcard VALUES (#{word}, #{definitions})") public void insert(@Param("word") String word, @Param("definitions") String definitions); @Select("select * from (select * from wordcard order by dbms_random.value) where rownum = 1") public WordCard selectOne(); }
매퍼 인터페이스를 MapperFactoryBean을 사용해 스프링에 추가한다.
<bean id="wordMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="net.java_school.mybatis.WordMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
서비스
WordCardService 클래스를 인터페이스로 바꾼다.
src/main/java/net/java_school/english/WordCardService.java
WordCardService.java
package net.java_school.english; public interface WordCardService { public void add(String word, String definitions); public WordCard getWordCard(); }
src/main/java/net/java_school/english/WordCardServiceImpl.java
WordCardServiceImpl.java
package net.java_school.english; import net.java_school.mybatis.WordMapper; import org.springframework.stereotype.Service; @Service public class WordCardServiceImpl implements WordCardService { private WordMapper wordMapper; public WordCardServiceImpl(WordMapper wordMapper) { this.wordMapper = wordMapper; } @Override public void add(String content) { wordMapper.insert(content); } @Override public WordCard getWordCard() { return wordMapper.selectOne(); }
<bean id="wordCardService" class="net.java_school.english.WordCardServiceImpl"> <constructor-arg ref="wordMapper" /> </bean>
컨트롤러
src/main/java/net/java_school/english/HomeController.java
HomeController.java
package net.java_school.english; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @Controller public class HomeController { private WordCardService wordCardService; public HomeController(WordCardService wordCardService) { this.wordCardService = wordCardService; } @GetMapping("/") public String homepage(Model model) { WordCard wordCard = wordCardService.getWordCard(); model.addAttribute("wordCard", wordCard); return "index"; } @PostMapping("/") public String add(String word, String definitions) { wordCardService.add(word, definitions); return "redirect:/"; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:resources mapping="/resources/**" location="/resources/" /> <mvc:annotation-driven /> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <!-- <bean id="wordCardDao" class="net.java_school.english.WordCardDao" /> --> <bean id="wordMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="net.java_school.mybatis.WordMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean> <bean id="wordCardService" class="net.java_school.english.WordCardServiceImpl"> <constructor-arg ref="wordMapper" /> </bean> <bean id="homeController" class="net.java_school.english.HomeController"> <constructor-arg ref="wordCardService" /> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" /> <property name="username" value="scott" /> <property name="password" value="tiger" /> <property name="maxActive" value="100" /> <property name="maxWait" value="1000" /> <property name="poolPreparedStatements" value="true" /> <property name="defaultAutoCommit" value="true" /> <property name="validationQuery" value=" SELECT 1 FROM DUAL" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
빌드와 배치
mvn clean compile war:inplace
테스트
http://localhost:8080
로깅
참고: https://mybatis.org/mybatis-3/ko/logging.html
아파치 commons-logging과 log4j 2 라이브러리를 의존성에 추가한다.
<!-- Logging --> <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.19.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.19.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-jcl --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> <version>2.19.0</version> </dependency>
log4j2.xml라는 이름으로 log4j 2 설정 파일을 src/main/resources/ 디렉터리에 생성한다.
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration> <Configuration> <Appenders> <File name="WordCard" fileName="WordCard.log" append="false"> <PatternLayout pattern="%t %-5p %c{2} - %m%n" /> </File> <Console name="STDOUT" target="SYSTEM_OUT"> <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" /> </Console> </Appenders> <Loggers> <Logger name="net.java_school" level="DEBUG"> <AppenderRef ref="WordCard" /> </Logger> <Root level="INFO"> <AppenderRef ref="STDOUT" /> </Root> </Loggers> </Configuration>
mvn clean compile war:inplace 실행해 컴파일하고 로그 메시지를 확인한다.
매퍼 XML 파일
여러 줄로 구성된 복잡한 쿼리는 XML 파일로 따로 분리하면 유지 보수에 좋다.
이 파일이 매퍼 XML 파일이다.
지금까지 마이바티스 설정 파일을 만들지 않았다.
매퍼 XML 파일과 매퍼 인터페이스를 같은 클래스패스에 둔다면, 마이바티스 설정 파일이 필요 없다.
참고: https://mybatis.org/spring/ko/mappers.html#register
WordMapper 인터페이스와 같은 클래스패스에 파일이 위치하도록, src/main/resources/net/java_school/mybatis 디렉터리에, WordMapper.xml 매퍼 파일을 생성한다.
src/main/resources/net/java_school/mybatis/WordMapper.xml
WordMapper.xml
<?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="net.java_school.mybatis.WordMapper"> <insert id="insert"> insert into wordcard values (#{word}, #{definitions}) </insert> <select id="selectOne" resultType="net.java_school.english.WordCard"> select * from (select * from wordcard order by dbms_random.value) where rownum = 1 </select> </mapper>
마이바티스 설정 파일에서 typeAlias 요소를 사용하면 매퍼 XML 파일에서 resultType="net.java_school.english.WordCard"를 resultType="WordCard"처럼 간단히 줄일 수 있다.
<-- XML 설정 파일에서 --> <typeAlias type="net.java_school.english.WordCard" alias="WordCard"/>
하지만 지금껏 생략했던 마이바티스 설정 파일을 추가해야 한다.
참고: https://mybatis.org/mybatis-3/ko/sqlmap-xml.html
자바 빈즈가 하나뿐이니, 마이바티스 설정 파일을 만들지 않고 진행한다.
WordMapper 인터페이스에서 쿼리를 제거한다.
WordMapper.java
package net.java_school.mybatis; import net.java_school.english.WordCard; import org.apache.ibatis.annotations.Param; public interface WordMapper { public void insert(@Param("word") String word, @Param("definitions") String definitions); public WordCard selectOne(); }
스프링 자동 스캔
스프링의 자동 스캔 기능을 사용하면 설정 파일 내용을 줄일 수 있다.
컨트롤러와 서비스는 <context:component-scan ... />로 스캔할 수 있다.
매퍼 인스턴스는 마이바티스가 생성하는 것이기에 위 설정으로 스캔할 수 없다.
<mybatis:scan ... />로 매퍼 인스턴스를 스캔한다.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.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"> <mvc:resources mapping="/resources/**" location="/resources/" /> <mvc:annotation-driven /> <context:component-scan base-package="net.java_school.english" /> <mybatis:scan base-package="net.java_school.mybatis" /> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" /> <property name="username" value="java" /> <property name="password" value="school" /> <property name="maxActive" value="100" /> <property name="maxWait" value="1000" /> <property name="poolPreparedStatements" value="true" /> <property name="defaultAutoCommit" value="true" /> <property name="validationQuery" value=" SELECT 1 FROM DUAL" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
<context:component-scan />를 사용하면 @Autowired 어노테이션도 함께 사용할 수 있다.
HomeController.java
import org.springframework.beans.factory.annotation.Autowired; @Controller public class HomeController { @Autowired private WordCardService wordCardService; /* 생성자 제거 public HomeController(WordCardService wordCardService) { this.wordCardSerivce = wordCardService; } */ //..omit.. }
WordCardServiceImpl.java
import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Autowired; @Service public class WordCardServiceImpl implements WordCardService { @Autowired private WordMapper wordMapper; /* 생성자 제거 public WordCardServiceImpl(WordMapper wordMapper) { this.wordMapper = wordMapper; } */ //..omit.. }
자바 기반 스프링 설정
다음 자바 파일을 생성한다.
AppConfig.java
package net.java_school.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class AppConfig implements WebMvcConfigurer { //TODO }
아래를 참고해 스프링 자바 설정 파일을 완성한다.
<mvc:annotation-driven />
⇩
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer {
<mvc:resources mapping="/resources/**" location="/resources/" cache-period="31556926" />
⇩
@Configuration @EnableWebMvc public class AppConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**") .addResourceLocations("/resources/") .setCacheControl(CacheControl.maxAge(Duration.ofDays(365))); } }
<context:component-scan base-package="net.java_school.english" />
⇩
@Configuration @EnableWebMvc @ComponentScan("net.java_school.english") public class AppConfig implements WebMvcConfigurer {
<mybatis:scan base-package="net.java_school.mybatis" />
⇩
@Configuration @EnableWebMvc @ComponentScan("net.java_school.english") @MapperScan("net.java_school.mybatis") public class AppConfig implements WebMvcConfigurer {
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> </bean>
⇩
@Bean public ViewResolver configureViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); viewResolver.setViewClass(JstlView.class); return viewResolver; }
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" /> <property name="username" value="scott" /> <property name="password" value="tiger" /> <property name="maxActive" value="100" /> <property name="maxWait" value="1000" /> <property name="poolPreparedStatements" value="true" /> <property name="defaultAutoCommit" value="true" /> <property name="validationQuery" value=" SELECT 1 FROM DUAL" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>
⇩
@Bean(destroyMethod = "close") public DataSource dataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver"); dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:XE"); dataSource.setUsername("scott"); dataSource.setPassword("tiger"); dataSource.setMaxActive(100); dataSource.setMaxWait(1000); dataSource.setPoolPreparedStatements(true); dataSource.setDefaultAutoCommit(true); dataSource.setValidationQuery("SELECT 1 FROM DUAL"); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); return sessionFactory.getObject(); }
AppConfig.java
package net.java_school.config; import java.time.Duration; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.http.CacheControl; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @Configuration @EnableWebMvc @ComponentScan("net.java_school.english") @MapperScan("net.java_school.mybatis") public class AppConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**") .addResourceLocations("/resources/") .setCacheControl(CacheControl.maxAge(Duration.ofDays(365))); } @Bean public ViewResolver configureViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); viewResolver.setViewClass(JstlView.class); return viewResolver; } @Bean(destroyMethod = "close") public DataSource dataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver"); dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:XE"); dataSource.setUsername("scott"); dataSource.setPassword("tiger"); dataSource.setMaxActive(100); dataSource.setMaxWait(1000); dataSource.setPoolPreparedStatements(true); dataSource.setDefaultAutoCommit(true); dataSource.setValidationQuery("SELECT 1 FROM DUAL"); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); return sessionFactory.getObject(); } }
web.xml 파일을 대신할 자바 설정 파일을 생성한다.
MyWebAppInitializer.java
package net.java_school.config; import javax.servlet.Filter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { AppConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected Filter[] getServletFilters() { return new Filter[] { new HiddenHttpMethodFilter() }; } }
다음처럼 설정해도 된다.
참고: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet-context-hierarchy
package net.java_school.config; import javax.servlet.Filter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { AppConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected Filter[] getServletFilters() { return new Filter[] { new HiddenHttpMethodFilter() }; } }
에러 페이지 매핑은 자바 설정으로 할 수 없기에, 추후 에러 페이지 매핑을 위해, web.xml 파일을 최소한의 내용으로 남겨둔다.
참고: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-customer-servlet-container-error-page
<?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"> </web-app>참고