MyBatis

3.MyBatis를 이용한 데이터 조회와 연동-log

Jungsoomin :) 2020. 7. 11. 23:30

sts로 스프링 레거시 프로젝트 >MVC 패키지를 만들고 pom.xml을 열어보다 문득 요상한 것을 발견했다.

<dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>4.1.0.RELEASE</version>
           <exclusions>
                <exclusion>
                     <groupId>commons-logging</groupId>
                     <artifactId>commons-logging</artifactId>
                </exclusion>
           </exclusions>
     </dependency>

<dependency> 자식태그로 <exclusions> 태그가 있고 또 후손태그로 <exclustion>태그가 있는데 이안에 특정 모듈이 정의 된 것 처럼 보이는 것이다... 이게 뭐일까..하고 찾아보았다.

 

  • <exclusions>태그 메이븐의 의존 설정 제외 태그이다. 

  • 즉 이 예에서는 spring-context-4.1.0.jar 파일은 commons-logging.jar 파일에 의존하고 있기에 의존전이가 일어난다는 것 같다. 그것을 제외시키는 태그로 dependency안에 쓰이나보다. 했다.

spring-context를 사용하지만 로깅 프레임워크를 commons-logging(log4j)가 아닌 logback(slf4j)을 사용하고 싶은 경우를 위해 지정하고 있다고 한다.

그러면 비워둔 로깅 프레임 워크를 다른 프레임 워크를 사용해주어야하는데, 문제가 있다면 log-back을 의존 설정에 추가해서 slf4j(로깅프레임워크를 연결하는 징검다리이다.)의 구현체인 logback을 설정해주자.

	<dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-classic</artifactId>
           <version>1.0.13</version>
     </dependency>

로그백의 사용법은 classpath 경로(ex: src/main/resources)logback.xml을 추가하고 기존과 똑같이 사용하면 된다.


이렇게 설정하고나면 src > main >resources 폴더의 log4j.xml에 에러가 난다.

이는 문법을 정의하는 DTD 파일을 로컬에서 찾는 중이기에 생기는 문제이다

DTD 파일이란 XML의 문법을 정의해 놓은 파일이며. XML은 DTD 파일을 먼저 확인하여 문법을 확인한다.

 

DTD 파일의 웹을 절대경로로 수정하면 해결된다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

 


이제 src/main/webapp/WEB-INF/spring/root-context를 찾아가 빈 등록 및 수정을 변경할 것이다.

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
			<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
			<property name="url" value="jdbc:mysql://127.0.0.1:3306/스키마명?useSSL=false&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC" ></property>
			<property name="username" value="유저명"></property>
			<property name="password" value="패스워드명"></property>		
		</bean>
		
		<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
			<property name="dataSource" ref="dataSource"></property>
			<property name="configLocation" value="classpath:/mybatis-config.xml"></property>
			<property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml"></property>
		</bean>
		
		<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
			<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
		</bean>

driverClassName에 net.sf.log4jdbc.sql.jdbcapi.DriverSpy 을 추가

SqlSessionTemplate의 setter로 configLocation 즉 mybatis 설정파일 , mapperLocations 즉 Mapper설정파일을 지정한다.

 

그 후에 SqlSessionTemplate 빈을 등록하여 생성자에 sqlSessionFactoryBean을 등록해준다.

 


이제 콘솔에 로그를 출력하기 위한 작업에 들어간다.

 

 

나중에 Could not load JDBC driver class [net.sf.log4jdbc.sql.jdbcapi.DriverSpy] 에러를 방지하기 위함과 콘솔에 쿼리문을 출력하기위해서 

<dependency>
   <groupId>org.bgee.log4jdbc-log4j2</groupId>
   <artifactId>log4jdbc-log4j2-jdbc4</artifactId>
   <version>1.16</version>
  </dependency>

해당 의존을 추가해주자.

 

 

다음으로 src/main/resources 아래에 log4jdbc.log4j2.properties 파일을 만들어 해당 내용을 기입하고 저장한다.

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

 

이후 src/main/resources 아래에 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>

log4j.xml 파일을 열고 priority의 value를 info 레벨로 설정한다.

<!-- Root Logger -->
	<root>
		<priority value="info" />
		<appender-ref ref="console" />
	</root>

이제 root-context에서 setter로 준 경로MyBatis 설정파일Mapper설정파일을 만들 차례이다.

 

설정 파일인 mybatis-config.xml는 src/main/resources 아래에 추가, mapper.xml 은 mappers 라는 폴더를 만들어 그 안에 추가를 하여 경로를 맞춰준다.


mybatis-config.xml의 내용이다.

   여기에는 추후 <typeAliases> 태그를 통해 VO 클래스를 등록해 둘 것이다. 이렇게 등록을 하면 mapper.xml 에서 VO 클래스를 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>
 
        
    </configuration>

memberMapper.xml의 내용이다.

   쿼리문을 입력하는 곳으로 보통 테이블당 1개의 mapper.xml을 작성한다. 

   namespace 속성은 DAO 객체의 경로를 풀네임으로 입력해준다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.soomin.dao.MemberDAO">
 
 
    
</mapper>

 

이후 자바 소스에서 com.soomin.cotroller 나 com.soomin.serive, com.soomin.dao, com.soomin.dto 등 구조에 맞는 패키지를 만들어준다.

 

Mapper.xml의 namepace에 맞게 com.soomin.dao 안에 MemeberDAO인터페이스를 만들 것이다. VO 객체는 com.soomin.dto 안에 만들 것이고.. 기억할 점VO객체의 프로퍼티명 테이블의 컬럼 명과 일치시켜주는 것이 좋다.

package com.soomin.dto;

public class MemberVO {
	private String id;
	private String pw;
	private String name;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPw() {
		return pw;
	}

	public void setPw(String pw) {
		this.pw = pw;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

DAO객체의 추상 메서드도 선언해주고...구현은 나중에 한다.

package com.soomin.dao;

import java.util.List;

import com.soomin.dto.MemberVO;

public interface MemberDAO {
	public List<MemberVO> selectMember() throws Exception;
}

이제 다시 src/main/resources 안의 mybatis-config.xml 파일을 열어 VO객체의 별칭을 추가하자.

<typeAliases>
            <typeAlias type="com.example.dto.MemberVO" alias="memberVO"/>
        </typeAliases>

맵핑할 VO객체가 더 있다면, 더 적어주면 되겠다. 이는 mapper.xml의 SQL 정의시 별칭을 이용한 정의를 가능하게끔 해준다.

 


이번에는 mappers 폴더의 Mapper.xml 파일을 수정할 것이다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.soomin.dao.MemberDAO">
 	<resultMap type="MemberVO" id="memberVOResult">
 		<result column="id" property="id"/>
 		<result column="pw" property="pw"/>
 		<result column="name" property="name"/>
 	</resultMap>
 	
 	<select id="selectMember" resultType="memberVO" ResultMap="memberVOResult">
 		SELECT ID,PW, NAME FROM TEST
 	</select>
 
    
</mapper>

 천천히 뜯어보자,

  1. <mapper>는 DAO 객체의 풀네임을 지정한다.

  2. <resultMap>은 매핑된 VO객체를 type 속성으로 지정, id 속성은 맵핑 객체의 Alias를 준다.

  3. <result>는 column 속성에 테이블의 컬럼명을 지정, property 속성에 VO객체의 프로퍼티명을 기술한다.

  4. 이후 <select><insert><delete><update>태그로 쿼리문을 실행하며 태그안에 내용에 쿼리문을 추가한다.

  5. 각 태그의 id 속성은 DAO 객체에 정의된 메서드 명을 따른다. resultType은 결과로 리턴할 것이다. resultMap에 정의한 id 명으로 설정해놨다.


이후 DAO 인터페이스를 만들고 mapper의 아이디 명과 같은 메서드를 등록한다. 리턴타입도 MemberVO며 reusltType에 정의된 Alias와 맞아떨어지니 알맞다고 할 수 있겠다.

 

이후 DAO 구현객체를 만든다.

package com.soomin.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.soomin.dto.MemberVO;

@Repository
public class MemberDAOImpl implements MemberDAO {
	@Autowired
	private SqlSession sqlSession;

	private final static String Namespace = "com.soomin.dao.MemberDAO";

	@Override
	public List<MemberVO> selectMember() throws Exception {
		return sqlSession.selectList(Namespace + ".selectMember");
	}
}

이런식이라고 할 수 있겠다. @Repository @Service @Controller 등은 다 @Component 에노테이션과 같다. 하지만 각 객체가 하는 역할을 확실히 명시해주기위해 사용한다고 기억하고 있다. 그렇다면 지금 필요한것은..? >>>> ComponentScan이다<<

 

Namespace값은 DAO정의 된 메서드를 사용할때 사용할 id, 즉 식별자다. 네임스페이스 값에 .메소드명을 달아주면 해당 구현객체의 메서드를 실행하게되는 것이다.


root-context.xml로 넘어가자. 해당 코드를 추가하는데, 살펴보자.

<context:component-scan base-package="com.soomin.dao"></context:component-scan>
<context:component-scan base-package="com.soomin.service"></context:component-scan>

<context:component-scan >@ComponentScan 에노테이션과 그냥 똑같다. 해당 패키지를 지정하여 그 패키지 아래의 모든 @Compoent들을 스캔해서 자동으로 Bean 객체로 만드는 것이다. 

 

Service 단의 Bean 객체들도 뷰와 관계가 없으므로 분리하여 root-context 에 둔다.

 

서비스인터페이스와 구현객체를 보자.

package com.soomin.service;

import java.util.List;

import com.soomin.dto.MemberVO;

public interface MemberService {
	List<MemberVO> selectMember() throws Exception;
}

 

ackage com.soomin.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.soomin.dao.MemberDAO;
import com.soomin.dto.MemberVO;

@Service
public class MemberServiceImpl implements MemberService {

	@Autowired
	private MemberDAO memberDAO;

	@Override
	public List<MemberVO> selectMember() throws Exception {
		return memberDAO.selectMember();
	}

}

Service는 DAO에 의존한다는 사실 기억한다. 그렇게 보면 Autowired를 이용해 주입을 하여 필드로 사용해야 바람직하며 서비스메서드를 재정의하여 해당 데이터를 맵핑하고 필요한 자료구조로 만들어주는 일들을 해야하는 것이다.

 


이제 Controller 단으로 간다.

package com.soomin.controller;

import java.util.List;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.soomin.dto.MemberVO;
import com.soomin.service.MemberService;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {

	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

	@Autowired
	private MemberService memberService;

	/**
	 * Simply selects the home view to render by returning its name.
	 * 
	 * @throws Exception
	 */
	@GetMapping("/")
	public String home(Locale locale, Model model) throws Exception {
		logger.info("home", locale);

		List<MemberVO> memberList = memberService.selectMember();
		model.addAttribute("memberList", memberList);

		return "home";
	}

}

간단한 코드이다. @Controller Component를 정의한다. 여기서 정말 중요한 점Controller는 서비스에 의존한다<는 것이다.

Service단에서 가공한 데이터를 model객체에 넣고 View이름을 리턴하여 ModelAndView객체로 HTTPServletRepuest에 넘기는 것이다.


이제 View단으로 움직이자.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
<h1>
	안녕!
</h1>
	<table>
		<thead>
			<tr>
				<th>아이디</th>
				<th>비밀번호</th>
				<th>이름</th>
			</tr>
		</thead>
		<tbody>
				<c:forEach items="${memberList}" var="member">
					<tr>
						<td>${member.id}</td>
						<td>${member.pw }</td>
						<td>${member.name }</td>
					</tr>
				</c:forEach>
			</tbody>
	</table>
</body>
</html>

만드느라 수고했다, 나한테 하는 말이다.

 


 

톰캣서버를 더블클릭하여 가서 context를 /로 바꿔주고 서버를 실행하면 

 

데이터베이스에서 값이 넘어오게 되어 이런 출력결과를 갖는다. 

 

에러잡고 아둥바둥하는데 험난했으나, MyBatis 연동이 드디어 성공했다.

'MyBatis' 카테고리의 다른 글

log4j.xml  (0) 2020.07.18
Mybatis, 테이블 연동과 기본적인 DML 조작  (0) 2020.07.18
4.Java 에노테이션으로 MyBatis 연동 및 테스트  (0) 2020.07.13
0.Basic.개발의 주요 구조.  (0) 2020.07.11
2..Mybatis의Test  (0) 2020.07.11