Helmi

구) 스프링 + Mybatis 실전 본문

Spring

구) 스프링 + Mybatis 실전

Helmi 2023. 4. 7. 17:36

참고 : 2023.04.06 - [Spring] - 스프링 + MyBatis 소개

 

스프링 + MyBatis 소개

MyBatis는 JDBC에서 개발자가 직접 처리하는 PreparedStatement의 '?'에 대한 설정이나 ResultSet 이용한 처리 이뤄짐. → 개발의 생산성이 좋아짐. 어노테이션 지원, 인터페이스와 어노테이션 통해 SQL문 설

helmi.tistory.com

더보기

MyBatis를 XML을 사용해 작성하는 경우 코딩 순서

1. 테이블 생성 및 개발 준비

2. 테이블 생성 및 기타 데이터베이스 관련 설정

3. 도메인 객체의 설계 및 클래스 작성

4. DAO 인터페이스 작성

5. 실행해야 하는 기능을 인터페이스로 정의

6. XML Mapper의 생성, SQL문 작성

7. XML 작성 및 SQL작성

8. MyBatis에서 작성된 XML Mapper를 인식하도록 설정

9. DAO 구현

10. DAO 인터페이스를 구현한 클래스 작성

11. 스프링상에 DAO 등록 및 테스트

 

1. 테이블 생성 및 개발 준비

테이블 설계 및 클래스 구성

 

1) 데이터 베이스의 테이블 생성

tbl_member 테이블 SQL

create table tbl_member(
userid varchar2(50) not null,
userpw varchar2(50) not null,
username varchar2(50) not null,
email varchar2(100),
regdate timestamp ,
updatedate timestamp ,
primary key(userid)
);

테이블 명은 일반적으로 'tbl_'로 시작하는 것이 좋음 (데이터베이스 내 여러 종류의 객체와 혼란 피하기 위해)

regdate : 최초 데이터 생긴 시점

updatedate  최종적으로 데이터 변경된 시점 기록

 

2) 도메인 객체 위한 클래스 설계

개발 시 가장 중요한 용어 될 만한 명사를 '도메인(domain)'이라 표현.

ex) 쇼핑몰 - 회원, 상품, 배송 등 용어

도메인 경우 1차 도메인, 2차 도메인.. 같이 가장 우선하고, 중요한 의미 가지는 순서 따라 정해짐. 어떤 비즈니스 로직에 있어 반드시 필요한 것이 1차 도메인, 점차 필요 로직 따라 2,3차로 확대 됨 

 

도메인 단위는 다른 의미로 여러 물리적 환경으로 분리 가능한 단위라 할 수 있음.

ex) 회원 DB, 상품 DB 분리해 운영할 수 있듯, 도메인은 하나의 온전한 시스템 단위 될 수 있음.

 

개발자에게 도메인 클래스는 일반적으로 특정 테이블과 유사한 속성 가지는 클래스 의미. 

 

org.zerock.domain.MemberVO

package org.zerock.domain;

import java.util.Date;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class MemberVO {
	
	private String userid;
	private String userpw;
	private String username;
	private String email;
	private Date regdate;
	private Date updatedate;

}

2. DAO 인터페이스 작성

실제 실행해햐 하는 작업을 인터페이스로 정의.

DAO로 작성하는 일은 향후에 데이터베이스 관련 기술이 변경되더라도, DAO만을 변경해 처리할 수 있도록 하기 위해.

 

일반적 경우, DAO는 'dao' 이름의 패키지 구성하거나, 'persistence'라는 패키지 작성. 

 

예제를 위해 간단한 데이터베이스의 현재 시간 체크 기능, tbl_member 테이블에 데이터 추가하는 기능 구성

 

org.zerock.persistence.MemberDAO

package org.zerock.persistence;

import org.zerock.domain.MemberVO;

public interface MemberDAO {
	
	public String getTime();
	
	public void insertMember(MemberVO vo);
}

3. XML Mapper 작성

DAO 클래스 작성되었다면 이를 사용하는 SQL문 작성해야 함.

MyBatis에서는 SQL문 저장하는 존재를 Mapper라는 용어로 표현

 

Mapper는 XML과 인터페이스 이용할 수 있음, XML 사용하는 경우라면 해당 순서로 진행

① XML로 작성된 Mapper의 위치(저장 경로) 설정

② XML Mapper 파일 작성하고 필요한 DTD 추가

③ SQL 작성

 

1) Mapper 파일의 저장 경로

XML 로 작성되는 Mapper  파일 경우, JAVA로 작성된 클래스와 경로 분리해주는 것이 유지보수에 있어 필수적

(java 파일 아닌 경우 'resources' 같은 경로 이용. Mapper 설정해두는 'mappers' 폴더 생성)

2) XML Mappers 작성

작성한 mappers 폴더에 'memberMapper.xml' 파일 생성

MyBatis 경우 작성된 XML Mapper는 상단 파일에서 사용하는 태그들에 대한 정보 기록된 DTD 추가.

(https://mybatis.org/mybatis-3/ko/getting-started.html) 참고

 

MyBatis – 마이바티스 3 | 시작하기

 

mybatis.org

 

resources/mappers.memberMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.zerock.mapper.MemberMapper">

	<select id="getTime" resultType="String">
		select now()
	</select>

	<insert id="insertMapper">
		insert into tbl_member (userid, userpw, username, email) values
		(#{userid}, #{userpw}, #{username}, #{email})
	</insert>

</mapper>

XML Mapper 작성 시, 'namespace' 설정에 가장 신경 많이 써야 함. (<mapper>)

'namespace' 속성 : 클래스의 패키지와 유사한 용도, Mybatis 내에 원하는 SQL문 찾아서 실행 시 동작

 

3) myBatis-Spring에서 XML Mapper 인식

MyBatis가 동작시 조금 전 작성된 XML Mapper 인식해야만 정상적 동작 가능. root-context.xml 이용해 설정 변경

 

root-context.xml

<bean id="sqlSessionFactory"
		class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		 <property name="configLocation" value="classpath:/mybatis-config.xml"></property>
		 <property name="mapperLocations" value="classpath:/mappers/**/*Mapper.xml"></property>
	</bean>

변화된 설정의 핵심은 mapperLocations라는 속성 추가, 작성된 mappers 폴더 내에 어떤 폴더이건 관계 없이 파일 이름이 'Mapper.xml'로 끝나면 자동으로 인식하도록 설정


4) DAO 인터페이스의 구현

DAO 인터페이스, Mapper의 작성 완료 후 실제 이를 구현하는 구현 클래스 작성해야 함. 

MyBatis에서 DAO 이용하는 경우는 SqlSessionTemplate 이용해 DAO 구현하므로, 우선적으로 SqlSessionTemplate 설정 작업부터 시작.

 

1) SqlSessionTemplate 설정

DAO의 가장 번거로운 작업은 데이터베이스와 연결 맺고, 작업 완료된 후 연결을 close()하는 작업.

 

다행히 mybatis-spring 라이브러리에는 이것 처리 가능한 SqlSessionTemplate 클래스 제공

이는 MyBatis의 SqlSession인터페이스를 구현한 클래스로 기본적인 트렌젝션의 관리나 쓰레드 처리의 안정성 등을 보장해주고, 데이터베이스의 연결과 종료 책임 짐.

SqlSessionFactory 생성자로 주입해 설정 함.

 

root-context.xml

<bean id="sqlSession"
class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
<constructor-arg name="sqlSessionFactory" ref="sqlSesionFactory"> </constructor-arg>
</bean>

 

2) 구현 클래스 작성

MemberDAO 인터페이스를 구현하는 클래스는 앞에서 설정된 SqlSessionTemplate을 주입받아 사용하도록 구성

 

org.zerock.persistence.MemberDAOImpl

package org.zerock.persistence;

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;
import org.zerock.domain.MemberVO;

@Repository
public class MemberDAOImpl implements MemberDAO {
	
	@Inject
	private SqlSession sqlSession;
	
	private static final String namespace = 
			"org.zerock.mapper.MemberMapper";
	
	@Override
	public String getTime() {
		return sqlSession.selectOne(namespace+".getTime");
	}
	@Override
	public void insertMember(MemberVO vo) {
		sqlSession.insert(namespace+".insertMember", vo);
	}
}

@Repository : DAO를 스프링에 인식시키기 위해서 주로 사용

 

MyBatis의 SqlSession에는 SQL문 처리할 수 있는 insert, update, delete 등 SQL 처리와 selectOne, selectList 같은 select 형태 지원함.


5. 스프링에 빈으로 등록하기

MemberDAOImpl이 @Repository 설정 되어 있어도 스프링에서 해당 패키지 스캔하지 않으면 제대로 스프링의 빈으로 등록되지 못함.

 

root-context.xml 일부

<context:component-scan base-package="org.zerock.persistence">
</context:component-scan>

전 →  후

설정된 패키지의 MemberDAOImpl에 스프링의 빈으로 등록되었다는 아이콘 보이는지 반드시 확인!


6. 테스트 코드 작성

최종적으로 작성된 코드는 테스트 과정 거쳐 설정과 동작 여부 확인함. 

 

/src/test 내 MemberDAOTest

package org.zerock.web;

import javax.inject.Inject;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.zerock.domain.MemberVO;
import org.zerock.persistence.MemberDAO;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class MemberDAOTest {
	
	@Inject
	private MemberDAO dao;
	
	@Test
	public void testTime()throws Exception{
		System.out.println(dao.getTime());
	}
	@Test
	public void testInsertMember()throws Exception{
		
		MemberVO vo = new MemberVO();
		vo.setUserid("user00");
		vo.setUserpw("1234");
		vo.setUsername("USER00");
		vo.setEmail("user00@aaa.com");
		
		dao.insertMember(vo);
	}
}

MemberDAOTest에서는 MemberDAO 주입 후, testTime() 메소드 사용해 MemberDAO 설정에 문제가 없는지 확인. 

실행 결과는 현재 데이터베이스의 시간이 출력 됨


7. MyBatis의 로그 log4jdbc-log4j2

MyBatis 사용해 개발하다 보면 가끔 잘못된 SQL이나 잘못된 속성 이름으로 예외 발생하기도 함.

이를 대비해 MyBatis의 로그를 보다 자세히 조사할 수 있도록 로그 설정해주는 것이 좋음.

(log4jdbc를 이용하는 경우 속도가 기존보다 저하 될 수 있음, 데이터베이스에 따라 지원되지 않는 경우 있으므로 설정 후 정상 동작 하는지 반드시 테스트 해주기!)

 

자세한 로그 보기 위해서는 우선 log4jdbc-log4j2 라이브러리 필요

 

pom.xml  파일에 추가

<!-- https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4.1 -->
	<dependency>
		<groupId>org.bgee.log4jdbc-log4j2</groupId>
		<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
		<version>1.16</version>
      </dependency>

 

라이브러리 추가된 후 데이터베이스와 연결되는 드라이버 클래스와 연결 URL을 수정해주기

 

root-context.xml 수정

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name = "driverClassName" value="net.sf.log4jdbc.sql.jbcapi.DriverSpy"></property>
	<property name="url" value="jdbc:log4jdbc:oracle:thin:@localhost:1521:xe"></property>
	
	<property name="username" value="book_ex"></property>
	<property name="password" value="1234"></property>
	</bean>

 

log4jdbc-log4j2가 제대로 동작하려면 별도의 로그 관련 설정 파일 필요.

/src/main/resources폴더에 'log4jbc/log4j2.properties' 파일과 'logback.xml' 파일 추가해야 함.

 

log4jbc/log4j2.properties

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>

<!--  log4jdbc-log4j2 -->
<logger name="jdbc.sqlonly" level="DEBUG"/>
<logger name="jdbc.sqltiming" level="INFO"/>
<logger name="jdbc.audit" level="WARN"/>
<logger name="jdbc.resultset" level="ERROR"/>
<logger name="jdbc.resultsettable" level="ERROR"/>
<logger name="jdbc.connection" level="INFO"/>
</configuration>

8. MyBatis의 #{}문법

새로운 사용자 등록과 조회 대한 처리

XML Mapper 사용하는 경우 가장 먼저 작업하는 내용은 DAO 인터페이스.

 

org.zerock.persistence.MemberDAO

package org.zerock.persistence;

import org.zerock.domain.MemberVO;

public interface MemberDAO {
	
	public String getTime();
	
	public void insertMember(MemberVO vo);
	
	public MemberVO readMember(String userid) throws Exception;
	
	public MemberVO readWithPW(String userid, String userpw) throws Exception;
}

작성된 인터페이스 메소드 이름을 기초로 XML Mapper 작성

 

resources/mappers 폴더 내 memberMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.zerock.mapper.MemberMapper">

	<select id="getTime" resultType="String">
		select now()
	</select>

	<insert id="insertMapper">
		insert into tbl_member (userid, userpw, username, email) values
		(#{userid}, #{userpw}, #{username}, #{email})
	</insert>
	
	<select id="selectMember" resultType="org.zerock.domain.MemberVO">
	select * from tbl_member where userid=#{userid}
	</select>

	<select id="readWithPw" resultType="org.zerock.domain.MemberVO">
	select * from tbl_member where userid=#{userid} and userpw = #{userpw}
	</select>
	
</mapper>

작성된 XML Mapper는 parameterType 생략한 채로 작성함

MyBatis 경우 기본적으로 PreparedStatement 이용해 처리 됨.

개발자가 PreparedStatement에 들어가는 파라미터 사용 시 '#{}' 기호 이용해 처리

 

#{} 규칙

- 파라미터가 여러 속성 가진 객체인 경우 '#{num}'은 getNum() 혹은 setNum() 의미

- 파라미터가 하나이고, 기본 자료형이나 문자열인 경우 값이 그대로 전달됨

- 파라미터가 Map 타입인 경우 '#{num}'은 Map 객체의 키 값이 'num'인 값 찾음

 

Mapper 인터페이스 구현한 클래스는 아래와 같이 작성

 

org.zerock.persistence.MemberDAOImpl

package org.zerock.persistence;

import java.util.HashMap;
import java.util.Map;

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;
import org.zerock.domain.MemberVO;

@Repository
public class MemberDAOImpl implements MemberDAO {
	
	@Inject
	private SqlSession sqlSession;
	
	private static final String namespace = 
			"org.zerock.mapper.MemberMapper";
	
	@Override
	public String getTime() {
		return sqlSession.selectOne(namespace+".getTime");
	}
	
	@Override
	public void insertMember(MemberVO vo) {
		sqlSession.insert(namespace+".insertMember", vo);
	}
	
	@Override
	public MemberVO readMember(String userid) throws Exception {
		return (MemberVO) sqlSession.selectOne(namespace+".selectMember", userid);
	}
	
	@Override
	public MemberVO readWithPW(String userid, String pw) throws Exception {
		
		Map<String, Object> paramMap = new HashMap<String, Object>();
		
		paramMap.put("userid", userid);
		paramMap.put("userpw", pw);
		
		return sqlSession.selectOne(namespace+".readWithPW", paramMap);
	}
}

readWithPW() 경우, 파라미터가 2개 이상 전달되는 경우이므로, Map 타입의 객체 구성해서 파라미터로 사용


9. 정리

PART 1 작업 정리하자면, 다음과 같은 순서로 이루어짐

① STS설치 (JDK 설치 필수)

② MySQL/Oracle 설치 및 설정

③ STS 프로젝트 생성

④ MySQL /Oracle 연동

⑤ STS 프로젝트 설정 변경 및 테스트

 

스프링 경우, 하나의 설정이 잘못된 경우에도 모든 동작 실행되지 않으므로 각 단계마다 어떤 결과가 나와야 하는지 명확히 알고 진행해야.

 

반드시 테스트 거쳐야 하는 항목들 지정하면 다음과 같음

① 프로젝트 생성 및 Tomcat 설정

MySQL /Oracle  데이터베이스 연동 테스트

③ MyBatis + String 연동 테스트

④ 스프링 MVC 컨트롤러 테스트

⑤ DAO, XML Mapper 연동 테스트

 

스프링 처음 접하는 사람들이 고생하는 대부분은 설정과 관련된 에러. 

테스트 꼭 해주세요~!!