Bigfat

[Spring] MyBatis 사용하기, SqlSession (게시판 구현) 본문

Java/web

[Spring] MyBatis 사용하기, SqlSession (게시판 구현)

kyou 2017. 4. 25. 19:59

MapperFactoryBean을 사용하지 않고, SqlSession을 주입해 사용해보자

  Mapper XML과 DAO Interface를 연결해주는 MapperFactoryBean을 사용하지 않고, SqlSession을 DAOImpl 클래스에 의존성을 주입(DI)해 사용해보자. MapperFactoryBean은 **DaoImpl 클래스가 필요 없는 대신 DAO 인터페이스가 늘어날 때마다 root-context.xml에 bean을 등록해줘야 하는 반면에 SqlSession을 **DaoImpl 클래스에 DI해 사용하면 bean 객체를 추가해주지 않아도 되지만 DAO 인터페이스를 구현하는 DAOImpl 클래스를 필요로 한다.


  root-context.xml을 열어 MapperFactoryBean 객체를 추가해줬던 코드를 주석 처리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    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">
    
    <!-- Root Context: defines shared resources visible to all other web components -->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:db.properties"></property>
    </bean>
    
    <bean id="dataSource"
            class="org.apache.tomcat.dbcp.dbcp2.BasicDataSource">
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <!-- <property name="validationQuery"></property> -->
    </bean>
    
    <bean id="sqlSessionFactory"
            class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:/mybatisConfig/mybatisConfig.xml"></property>
    </bean>
 
    <bean id="sqlSessionTemplate"
            class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory"></constructor-arg>
    </bean>
    
    <!-- Mapper를 만드는 Bean -->
    <!-- 단점: 인터페이스가 늘어날수록 bean을 등록해줘야한다. -->
    <!-- 
    <bean id="bbsDao"
            class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.edu.bbs.dao.BBSDao"></property>
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate"></property>
    </bean>
    
    <bean id="commentDao"
            class="org.mybatis.spring.mapper.MapperFactoryBean">
            <property name="mapperInterface" value="com.edu.comment.dao.CommentDao"></property>
            <property name="sqlSessionTemplate" ref="sqlSessionTemplate"></property>
    </bean> -->
    
    <!-- <bean id="saveDir" class="java.lang.String">
        <constructor-arg value="d:\upload\" />    
    </bean>
    -->
    
</beans>
cs


  BBSDaoImpl 클래스를 아래와 같이 작성한다. namespace는 Mapper XML의 namespace와 일치해야 하는 것을 주의하자. SqlSession의 구문을 실행하는 메서드는 selectOne, selectList, select, selectMap, insert, update, delete가 있다. selectOne 메서드는 한 개 이상의 객체나 null이 return된다면 예외가 발생하므로, 객체가 얼마나 리턴될지 모른다면 selectList 메서드를 사용해야 한다. insert, update, delete 메서드에 의해 리턴되는 값은 실행된 구문에 의해 영향을 받은 레코드수를 표시한다(참고1). Mapper XML인 bbs.xml은 그대로 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.edu.bbs.dao;
 
import java.util.HashMap;
import java.util.List;
 
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
import com.edu.bbs.dto.BBSDto;
 
@Repository
public class BBSDaoImpl implements BBSDao {
    
    // SqlSessionTemplate DI
    @Autowired
    protected SqlSessionTemplate sqlSession;
    
    // Mapper XML의 namespace와 일치해야 한다.
    private static String namespace = "com.edu.bbs.dao.BBSDao";
    
    @Override
    public List<BBSDto> selectArticles(HashMap<String, Object> paramMap) {
        return sqlSession.selectList(namespace+".selectArticles", paramMap);
    }
 
    @Override
    public int getArticleTotalCount() {
        return sqlSession.selectOne(namespace+".getArticleTotalCount");
    }
 
    @Override
    public BBSDto selectArticle(String articleNumber) {
        return sqlSession.selectOne(namespace+".selectArticle", articleNumber);
    }
 
    @Override
    public int upHit(String articleNumber) {
        return sqlSession.update(namespace+".upHit", articleNumber);
    }
 
    @Override
    public String loginCheck(String id) {
        return sqlSession.selectOne(namespace+".loginCheck", id);
    }
 
    @Override
    public int insertArticle(BBSDto article) {
        return sqlSession.insert(namespace+".insertArticle", article);
    }
 
    @Override
    public int upPos(BBSDto article) {
        return sqlSession.update(namespace+".upPos", article);
    }
 
    @Override
    public int replyArticle(BBSDto article) {
        return sqlSession.insert(namespace+".replyArticle", article);
    }
 
    @Override
    public int updateArticle(BBSDto article) {
        return sqlSession.update(namespace+".updateArticle", article);
    }
 
    @Override
    public int deleteArticle(String articleNumber) {
        return sqlSession.delete(namespace+".deleteArticle", articleNumber);
    }
 
    @Override
    public int countComments(String articleNumber) {
        return sqlSession.selectOne(namespace+".countComments", articleNumber);
    }
 
}
cs




 댓글 쓰기 구현하기에서 컨트롤러를 최대한 깔끔하게 하고자 로직을 모두 서비스에 박아놨었는데, 이후 프로젝트의 진행과 테스트에 있어 상당히 불편한 점이 많았다. 그래서 CommentDaoImpl 클래스를 추가하면서 Comment 관련 자바 코드를 모두 수정하였다.


  CommentController는 아래와 같이 수정하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.edu.comment.controller;
 
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
 
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.edu.comment.dto.CommentDto;
import com.edu.comment.service.CommentService;
 
@Controller
public class CommentController {
    
    @Autowired
    private CommentService commentService;
    
    HashMap<String, Object> paramMap;
    
    @RequestMapping("/commentWrite.comment")
    public String writeAndReadComments(HttpSession session, HttpServletResponse resp, CommentDto comment) {
        comment.setId(session.getAttribute("id").toString());
        
        paramMap = new HashMap<>();
        paramMap.put("result", commentService.writeComment(comment));
        paramMap.put("comments", commentService.readComments(comment.getArticleNumber(), 10));
        sendJsonObject(resp, paramMap);
        
        return null;
    }
    
    @RequestMapping("/commentRead.comment")
    public String readComments(HttpServletResponse resp, int articleNumber, int commPageNum) {
        this.sendJsonObject(resp, commentService.readComments(articleNumber, commPageNum));
        
        // return ""로 하면 404에러 때문에 데이터 안 넘어간다.
        return null;
    }
 
    @SuppressWarnings("unchecked")
    public void sendJsonObject(HttpServletResponse resp, Object obj) {
        resp.setCharacterEncoding("utf-8");
        PrintWriter pw = null;
        JSONObject jsonObj = null;
        JSONArray jsonArr = null;
        
        try {
            pw = resp.getWriter();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        if(obj instanceof HashMap) {
            jsonObj = new JSONObject((HashMap<String, Object>) obj);
            pw.println(jsonObj);
        } else if(obj instanceof List<?>) {
            jsonArr = new JSONArray((List<CommentDto>) obj);
            pw.println(jsonArr);
        }
    }
}
cs


  서비스단에서 모든 로직을 수행하고 데이터를 HashMap에 담아 리턴해주던 기존의 방식을 버리고, 컨트롤러에서 writeComment, readComments 메서드를 둘 다 호출하도록 한다. readComments 메서드의 리턴 타입을 ArrayList<CommentDto>에서 List<CommentDto>로 변경하였는데, 이는 CommentDaoImpl 클래스 때 설명하도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.edu.comment.service;
 
import java.util.List;
 
import com.edu.comment.dto.CommentDto;
 
public interface CommentService {
 
//    public HashMap<String, Object> writeAndReadComments(CommentDto comment);
    
    public int writeComment(CommentDto comment);
    
    public List<CommentDto> readComments(int articleNumber, int commPageNum);
    
}
cs


  CommentServiceImpl 클래스는 아래와 같이 기존의 writeAndReadComments 메서드를 writeComment 메서드로 변경하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.edu.comment.service;
 
import java.util.HashMap;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.edu.comment.dao.CommentDao;
import com.edu.comment.dto.CommentDto;
 
@Service
public class CommentServiceImpl implements CommentService {
 
    @Autowired
    private CommentDao commentDao;
 
    HashMap<String, Object> paramMap;
    
//    @Override
//    public HashMap<String, Object> writeAndReadComments(CommentDto comment) {
//        int result = commentDao.writeComment(comment);
//        
//        resultMap = new HashMap<>();
//        resultMap.put("result", result);
//        resultMap.put("comments", this.readComments(comment.getArticleNumber(), 10));
//        
//        return resultMap;
//    }
 
    @Override
    public int writeComment(CommentDto comment) {
        return commentDao.writeComment(comment);
    }
    
    @Override
    public List<CommentDto> readComments(int articleNumber, int commPageNum) {
        paramMap = new HashMap<>();
        paramMap.put("articleNumber", articleNumber);
        paramMap.put("commPageNum", commPageNum);
        
        return commentDao.readComments(paramMap);
    }
    
}
cs


  CommentDao 인터페이스는 readComments 메서드의 리턴 타입만 변경하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.edu.comment.dao;
 
import java.util.HashMap;
import java.util.List;
 
import com.edu.comment.dto.CommentDto;
 
public interface CommentDao {
    
    public int writeComment(CommentDto comment);
    
    public List<CommentDto> readComments(HashMap<String, Object> paramMap);
    
}
cs


  CommentDaoImpl 클래스를 아래와 같이 작성하였다. Service와 Dao의 readComments 메서드의 반환 타입을 ArrayList<CommentDto>에서 List<CommentDto>로 바꿔준 것은 SqlSession의 selectList 메서드의 리턴 값이 List<Object>이기 때문이다. 그렇기에 readComments 메서드 내에 주석 처리해둔 것처럼 코드가 한 줄 늘어나거나, 서비스단에서 다운캐스팅 해줘야하는 불편함이 있다(BBSServiceImpl을 보면 다운캐스팅 해놓은 것을 볼 수 있다).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.edu.comment.dao;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
 
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
import com.edu.comment.dto.CommentDto;
 
@Repository
public class CommentDaoImpl implements CommentDao {
 
    @Autowired
    protected SqlSessionTemplate sqlSession;
    
    private static String namespace = "com.edu.comment.dao.CommentDao";
    
    @Override
    public int writeComment(CommentDto comment) {
        return sqlSession.insert(namespace+".writeComment", comment);
    }
 
    @Override
    public List<CommentDto> readComments(HashMap<String, Object> paramMap) {
//        List<CommentDto> result = sqlSession.selectList(namespace+".readComments", paramMap);
//        return (ArrayList<CommentDto>) result;
        return sqlSession.selectList(namespace+".readComments", paramMap);
    }
 
}
cs



[MyBatis 한글 문서, SqlSession 참고1]