개요
안녕하세요. J4J입니다.
이번 포스팅은 스프링을 이용한 MVC패턴 구현 중 세 번째인 MySQL, MyBatis, @Repository구성에 대해 적어보는 시간을 가져보려고 합니다.
들어가기에 앞서
이번 포스팅은 위의 MVC구조에서 빨간 박스에 포함되는 내용에 대해 적어보고자 합니다.
DB는 MySQL을 사용할 것이고 MyBatis를 이용하여 DB와 Repository를 연결하는 설정을 해볼 예정입니다.
@Repository란?
@Repository는 스프링의 비즈니스 로직 처리에서 데이터베이스에 접근하는 구간입니다.
일반적으로 자바에서 사용되는 방식으로 데이터베이스에 접근해도 되지만 스프링에서는 주로 MyBatis라는 것을 이용하여 데이터베이스에 접근합니다.
MyBatis란?
MyBatis는 MySQL, Oracle과 같은 관계형 데이터베이스 프로그래밍을 효율적으로 개발할 수 있도록 도와주는 개발 프레임워크입니다.
MyBatis를 굳이 사용하지 않아도 문제 될 것은 없지만 사용할 경우 DB동작을 위한 쿼리와 프로그램 코드를 서로 분리시켜 코드의 간결성 및 유지보수성을 높여주는 장점을 가지고 있습니다.
MyBatis에 관련된 자료는 MyBatis – 마이바티스 3 | 소개 - MyBatis.org에서 추가적으로 확인하실 수 있습니다.
프로젝트 설정
※ 스프링과 관련된 코드는 모두 STS-3.9.12.RELEASE 버전을 기준으로 작성되었습니다.
[ 0. MySQL 설치 ]
[ 1. MySQL 계정 접속 후 database생성 및 카트에 담긴 물건을 보관할 cart table생성 ]
-- DB 쿼리
create database mvc;
use mvc;
create table cart (
name varchar(50),
price int,
count int
);
set charset utf8;
alter database mvc default character set utf8;
alter table cart convert to character set utf8;
[ 2. 스프링으로 돌아와 pom.xml에 dependency 추가 후 maven update (maven update를 모르신다면 이전 포스팅 참고) ]
<?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 https://maven.apache.org/maven-v4_0_0.xsd">
...
<properties>
<java-version>1.8</java-version>
<org.springframework-version>5.2.11.RELEASE</org.springframework-version>
<org.aspectj-version>1.9.6</org.aspectj-version>
<org.slf4j-version>1.7.30</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
...
<!-- MySQL, MyBatis, @Repository -->
<dependency> <!-- 스프링에서 DB처리를 위한 dependency -->
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency> <!-- MySQL 사용을 위한 dependency -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency> <!-- MyBatis 사용을 위한 dependency -->
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency> <!-- 스프링에서 MyBatis 사용을 위한 dependency -->
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency> <!-- 커넥션풀을 위한 dependency -->
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.8.0</version>
</dependency>
...
</dependencies>
...
</project>
[ 3. root-context.xml의 Namespaces에서 context, mvc 체크 ]
[ 4. root-context.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan
base-package="com.spring.mvc.repository" /> <!-- repository관련 클래스 등록 패키지 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!-- mysql 연동을 위한 dataSource bean 등록 -->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mvc?serverTimezone=UTC"></property> <!-- mvc는 위에서 생성한 database이름 -->
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- mybatis 사용을 위한 sessionFactory bean 등록 -->
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- mybatis 설정파일 등록 -->
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!-- mybatis 사용을 위한 sqlSession bean 등록 -->
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
</beans>
[ 5. MyBatis설정을 위한 xml파일 생성 (src/main/resources/mybatis-config.xml) ]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="com.spring.mvc.dto.Cart" alias="cart"/> <!-- Cart클래스의 이름을 cart로 치환 -->
</typeAliases>
<mappers>
<mapper resource="mapper/cartmapper.xml" /> <!-- Cart관련 쿼리 파일 등록-->
</mappers>
</configuration>
[ 6. Cart table에 데이터 삭제와 데이터 조회를 위한 쿼리 파일 생성 (src/main/resources/mapper/cartmapper.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="com.spring.mvc.cartmapper"> <!-- Repository와 연동을 위해 사용될 namespace -->
<insert id="insertCart" parameterType="cart"> <!-- Cart클래스를 변수로 받아와 쿼리에 사용 -->
insert
into cart(name, price, count)
values (#{name}, #{price}, #{count})
</insert>
<select id="selectCart" resultType="cart"> <!-- 조회된 값을 Cart클래스에 담아 return -->
select name, price, count
from cart
</select>
</mapper>
mapper파일에서 사용되는 CRUD를 위한 태그는 insert, select, delete, update가 있습니다.
태그 내부에 사용되는 속성들 중 대표적으로 parameterType과 resultType이 있는데 parameterType은 쿼리에 사용될 파라미터의 타입을 의미하고 resultType은 쿼리의 결과가 리턴되는 타입을 의미합니다.
paramterType으로 DTO(Data Transfer Object)로 불리는 데이터 전달 객체 클래스를 가져올 경우에는 클래스 내부 변수들의 값을 #{변수명}의 형태로 쿼리에 적용시킬 수 있습니다.
또한 parameterType과 resultType에서 사용되는 DTO들은 모두 getter, setter가 정의되어 있어야 합니다.
[ 7. cart데이터가 저장될 DTO클래스 생성 (com.spring.mvc.dto.Cart) ]
package com.spring.mvc.dto;
public class Cart {
private String name;
private int price;
private int count;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "Cart [name=" + name + ", price=" + price + ", count=" + count + "]";
}
}
[ 8. DB와 연동될 Repository 인터페이스 및 클래스 생성 (com.spring.mvc.repository.CartRepository / com.spring.mvc.repository.CartRepositoryImpl ) ]
package com.spring.mvc.repository;
import java.util.List;
import com.spring.mvc.dto.Cart;
public interface CartRepository {
public int insertCart(Cart cart); // cart데이터 저장 용도
public List<Cart> selectCart(); // cart데이터 조회 용도
}
package com.spring.mvc.repository;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.spring.mvc.dto.Cart;
@Repository // 해당 클래스를 bean으로 등록
public class CartRepositoryImpl implements CartRepository {
private String namespace = "com.spring.mvc.cartmapper."; // mapper파일의 namespace + "."
@Autowired
SqlSession session;
@Override
public int insertCart(Cart cart) {
return session.insert(namespace + "insertCart", cart); // return 형태: session.{id의 태그명}(namespace + {namespace에 속하는 mapper의 id}, {parameterType})
}
@Override
public List<Cart> selectCart() {
return session.selectList(namespace + "selectCart"); // selectList는 조회되는 값을 모두 불러와 List에 저장, selectOne은 조회되는 값 하나만 저장
}
}
설정 테스트
위의 설정이 정상적으로 되었는지 단위 테스트를 해보겠습니다.
※ 단위 테스트에 대해 모르신다면? [Spring] 단위 테스트(JUnit Test)
로그 레벨은 debug단계로 변경하여 MyBatis를 통해 실행되는 쿼리까지 같이 출력되도록 해보겠습니다.
[ 1. 로그레벨 변경 (src/test/resources/log4j.xml) ]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- Appenders -->
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p: %c - %m%n" />
</layout>
</appender>
<!-- Application Loggers -->
<logger name="com.spring.mvc">
<level value="debug" />
</logger>
<!-- 3rdparty Loggers -->
<logger name="org.springframework.core">
<level value="debug" />
</logger>
<logger name="org.springframework.beans">
<level value="debug" />
</logger>
<logger name="org.springframework.context">
<level value="debug" />
</logger>
<logger name="org.springframework.web">
<level value="debug" />
</logger>
<!-- Root Logger -->
<root>
<priority value="info" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
[ 2. 테스트를 위한 클래스 생성(com.spring.mvc.MVC) ]
package com.spring.mvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.spring.mvc.dto.Cart;
import com.spring.mvc.repository.CartRepository;
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml"})
public class MVC {
private static Logger logger = LoggerFactory.getLogger(MVC.class);
@Autowired
CartRepository cartRepositoryImpl;
@Test
public void DBTest() {
Cart cart = new Cart();
cart.setName("김치");
cart.setPrice(1500);
cart.setCount(4);
logger.debug(cartRepositoryImpl.insertCart(cart) + ""); // Cart 데이터 등록
logger.debug(cartRepositoryImpl.selectCart().toString()); // Cart 데이터 조회
}
}
[ 3. 테스트를 진행할 경우 아래와 같은 로그가 나오며 데이터가 저장되고 저장된 데이터의 조회도 정상적으로 동작합니다. ]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@6ebc05a6, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6e6c3152, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@50b494a6, org.springframework.test.context.support.DirtiesContextTestExecutionListener@3cef309d, org.springframework.test.context.transaction.TransactionalTestExecutionListener@32709393, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@3d99d22e, org.springframework.test.context.event.EventPublishingTestExecutionListener@49fc609f]
DEBUG: org.springframework.core.env.StandardEnvironment - Activating profiles []
DEBUG: org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\User\Desktop\tistory_spring\mvc\target\classes\com\spring\mvc\repository\CartRepositoryImpl.class]
DEBUG: org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 9 bean definitions from URL [file:src/main/webapp/WEB-INF/spring/root-context.xml]
DEBUG: org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@7e2d773b
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cartRepositoryImpl'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSession'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactory'
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
DEBUG: com.spring.mvc.cartmapper.insertCart - ==> Preparing: insert into cart(name, price, count) values (?, ?, ?)
DEBUG: com.spring.mvc.cartmapper.insertCart - ==> Parameters: 김치(String), 1500(Integer), 4(Integer)
DEBUG: com.spring.mvc.cartmapper.insertCart - <== Updates: 1
DEBUG: com.spring.mvc.MVC - 1
DEBUG: com.spring.mvc.cartmapper.selectCart - ==> Preparing: select name, price, count from cart
DEBUG: com.spring.mvc.cartmapper.selectCart - ==> Parameters:
DEBUG: com.spring.mvc.cartmapper.selectCart - <== Total: 1
DEBUG: com.spring.mvc.MVC - [Cart [name=김치, price=1500, count=4]]
DEBUG: org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@7e2d773b, started on Sat Feb 13 20:59:22 KST 2021
[ 4. MySQL에서도 쿼리를 조회해보면 데이터가 저장된 것을 확인할 수 있습니다. ]
RootContext 설정 파일 변경(xml → Java)
위의 프로젝트 설정에서 root-context의 설정 파일을 xml파일 대신 java파일로 변경하여 설정할 수 있습니다.
java파일로 설정하는 것이 xml보다 더 최근에 등장했기 때문에 xml보다는 java가 더 최신 트렌드(?)라고 생각됩니다.
다른 파일들은 변경할 필요 없고 root-context.xml에 대해서만 아래와 같이 변경해주면 정상적으로 동작합니다.
[ 1. root-context.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- java 방식 -->
<bean class="com.spring.mvc.config.RootContext"></bean>
<!-- xml 방식 -->
<!--
<context:component-scan
base-package="com.spring.mvc.repository" /> repository관련 클래스 등록 패키지
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> mysql 연동을 위한 dataSource bean 등록
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mvc?serverTimezone=UTC"></property> mvc는 위에서 생성한 database이름
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> mybatis 사용을 위한 sessionFactory bean 등록
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" /> mybatis 설정파일 등록
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> mybatis 사용을 위한 sqlSession bean 등록
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
-->
</beans>
[ 2. RootContext클래스 파일 생성 (com.spring.mvc.config.RootContext) ]
package com.spring.mvc.config;
import org.apache.commons.dbcp2.BasicDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
@Configuration // 설정파일로 등록
@ComponentScan(basePackages = {"com.spring.mvc.repository"})
public class RootContext {
@Bean
public BasicDataSource dataSource() { // mysql 연동을 위한 dataSource bean 등록
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mvc?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception { // mybatis 사용을 위한 sessionFactory bean 등록
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource());
sqlSessionFactory.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml")); // mybatis 설정파일 등록
return sqlSessionFactory;
}
@Bean
public SqlSessionTemplate sqlSession(SqlSessionFactoryBean sqlsessionFactory) throws Exception { // mybatis 사용을 위한 sqlSession bean 등록
return new SqlSessionTemplate(sqlsessionFactory.getObject());
}
}
[ 3. 단위 테스트 ]
package com.spring.mvc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.spring.mvc.config.RootContext;
import com.spring.mvc.dto.Cart;
import com.spring.mvc.repository.CartRepository;
@RunWith(SpringRunner.class)
// @ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml"}) // xml 방식
@ContextConfiguration(classes = RootContext.class) // java 방식
public class MVC {
private static Logger logger = LoggerFactory.getLogger(MVC.class);
@Autowired
CartRepository cartRepositoryImpl;
@Test
public void DBTest() {
Cart cart = new Cart();
cart.setName("김치");
cart.setPrice(1500);
cart.setCount(4);
logger.debug(cartRepositoryImpl.insertCart(cart) + ""); // Cart 데이터 등록
logger.debug(cartRepositoryImpl.selectCart().toString()); // Cart 데이터 조회
}
}
파일 구성
참조
[MyBatis] MyBatis란? 개념 및 데이터구조
정리
이상으로 스프링을 이용한 MVC패턴 구현 중 세 번째인 MySQL, MyBatis, @Repository구성에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'Spring > Spring' 카테고리의 다른 글
[Spring] 스프링을 이용한 MVC패턴 구현(5) - @Controller 구성 (0) | 2021.02.18 |
---|---|
[Spring] 스프링을 이용한 MVC패턴 구현(4) - @Service 구성 (0) | 2021.02.16 |
[Spring] 스프링을 이용한 MVC패턴 구현(2) - 프로젝트 초기 설정 방법 (0) | 2021.02.11 |
[Spring] 스프링을 이용한 MVC패턴 구현(1) - Spring MVC (0) | 2021.02.09 |
[Spring] 제어의 역전(IoC), 의존성 주입(DI) (4) | 2021.02.07 |
댓글