본문 바로가기
Programming/>> Spring

[Spring] Spring Mybatis 설정 2

by 니키ᕕ( ᐛ )ᕗ 2016. 2. 23.

mybatis mapper의 자세한 설정에 대해서는 아무래도 어중간한 설명을 쓰는 것보다는 공식 문서를 읽는게 좋은 것 같다. 거기다 한글이니 읽는데는 무리가 없을 듯.

 

대신 Spring validator를 해보면서 만든 화면을 가지고 테스트를 해보았다. validator는 여기를 참고한다.

 

 

1. Sign up 화면을 불러올 때, 관심분야는 DB에서 불러온다.

2. Submit을 눌렀을 때, 유효성 검사를 하는데, 아이디 중복여부도 함께 체크한다.

3. DB에 사용자 정보를 추가한다. 의 순서로 진행된다. 

 

 

 

 

1. 조건없는 Select

 

Mybatis를 쓰는 가장 큰 이유는 Java bean과의 매핑이 유용하기 때문이다. 별다른 설정을 하지 않아도 hashmap형태로 받을 수 있고 VO나 자체적으로 resultmap을 설정할 수도 있다.

 

 

- DB

 

CREATE TABLE `TB_INTEREST` (
  `INTEREST_ID` int(11) NOT NULL AUTO_INCREMENT,
  `INTEREST_NAME` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`INTEREST_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

 

 

우선 TB_INTEREST에서 INTEREST_ID랑 INTEREST_NAME을 가져온다. 테이블 구조와 데이터는 위와 같이 구성되어있다.

 

 

- DAO

@Repository
public class MemberDaoImpl implements MemberDao {
	
	String namespace = "mappers.MemberMapper.";

    @Autowired
    SqlSessionTemplate session;
    
	@Override
	public List<Map<Integer, String>> getInterests() {
		return session.selectList(namespace + "getInterests");
	}
}

root-context.xml에서 추가한 SessionTemplate 지정하는데, DB를 여러개 쓴다면 @Autowired("bean이름")으로 여러개를 주입해줄 수 있다. SessionTemplate 클래스를 이용하여 mybatis mapper 파일에서 정한 SQL문을 호출한다. 

SessionTemplate에서 제공하는 메소드의 첫번째 파라미터에서는 어떤 mapper 파일의 내용을 불러올지를 각 mapper의 첫줄에 지정한 mapper namespace + '.' + 각 구문의 아이디의 형태로 선택할 수 있다.

메소드의 종류는 구문에 따라 달라진다. 이번의 경우에는 SELECT로 리스트를 뽑아오는 것이므로 selectList를 사용했다.

 

- Service

@Service
public class MemberServiceImpl implements MemberService {

	@Autowired
	MemberDao dao;
	
	public List<Map<Integer, String>> getInterests() {
		return dao.getInterests();
	}
}

Service에 DAO를 지정하고 해당 서비스의 getInterests 메소드로부터 DB에 저장된 내용을 호출한다.

 

- Controller

@Controller
@RequestMapping(value="/member")
public class MemberController {
	
	@Autowired
	MemberService service;
	
	@RequestMapping(value="/join", method=RequestMethod.GET)
	public String gojoin(Model model){
		model.addAttribute("interests", service.getInterests());
		model.addAttribute("user", new User());
		return "member/member_join";
	}
}

Controller에 비즈니스 로직인 Service를 지정하고 해당 서비스의 getInterests 메소드로부터 DB에 저장된 내용을 호출한다.

 

ⓛ ResultMap 설정 X

- Mapper

	<select id="getInterests" resultType="hashmap">
		SELECT
			*
		FROM
			TB_INTEREST
	</select>

별다른 설정없이 resultType을 hashmap으로 지정한다.

 

- JSP

				  <c:set var="erInterests"><form:errors path="interests" /></c:set>
				  <div class="form-group">
				  	  <label><spring:message code="join.interests"/></label>
					  <div class="checkbox">
					  	<c:forEach var="interest" items="${interests}">
					  		<label class="checkbox-inline">
							  <form:checkbox path="interests" value="${interest.INTEREST_ID}" /> ${interest.INTEREST_NAME}
							</label>
					  	</c:forEach>
					  </div>
					  <c:if test="${not empty erInterests}"><span class="has-error-msg">${erInterests}</span></c:if>
				  </div>

다만 이 경우에는 hashmap의 키값으로 컬럼명을 그대로 사용한다.

 

 

② ResultMap 설정 

- Mapper

<resultMap id="interestMap" type="java.util.HashMap">
  <id property="id" column="INTEREST_ID" />
  <result property="name" column="INTEREST_NAME"/>
</resultMap>	
<select id="getInterests" resultMap="interestMap">
		SELECT
			*
		FROM
			TB_INTEREST
	</select>

resultMap을 이용하여 컬럼명과 다른 key값을 사용할 수 있다.

 

- JSP

				  <c:set var="erInterests"><form:errors path="interests" /></c:set>
				  <div class="form-group">
				  	  <label><spring:message code="join.interests"/></label>
					  <div class="checkbox">
					  	<c:forEach var="interest" items="${interests}">
					  		<label class="checkbox-inline">
							  <form:checkbox path="interests" value="${interest.id}" /> ${interest.name}
							</label>
					  	</c:forEach>
					  </div>
					  <c:if test="${not empty erInterests}"><span class="has-error-msg">${erInterests}</span></c:if>
				  </div>

resultMap에서 지정한 프로퍼티 이름을 키로 값을 얻을 수 있다.

 

 

2. 조건있는 Select

 

이번엔 파라미터가 들어가는 SELECT를 해본다. 회원가입시 중복체크를 위해서지만 아이디로 회원정보를 가져오는 쿼리를 사용한다.

 

 

- DB

CREATE TABLE `TB_USER` (
  `userno` int(11) NOT NULL AUTO_INCREMENT,
  `USERID` varchar(30) CHARACTER SET utf8 NOT NULL,
  `PASSWORD` varchar(50) CHARACTER SET utf8 NOT NULL,
  `NAME` varchar(25) CHARACTER SET utf8 NOT NULL,
  `EMAIL` varchar(200) CHARACTER SET utf8 DEFAULT NULL,
  `POINT` smallint(6) DEFAULT '0',
  `RANK` tinyint(10) DEFAULT '0',
  `REGDATE` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `LASTDATE` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `GENDER` enum('M','F') COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`userno`,`USERID`),
  UNIQUE KEY `userid_UNIQUE` (`USERID`),
  UNIQUE KEY `userno_UNIQUE` (`userno`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

TB_USER는 위와 같이 구성되어있다.

 

 

- Service

@Service
public class MemberServiceImpl implements MemberService {

	@Autowired
	MemberDao dao;
	
	public User selectUser(String userid) {
		return dao.getUser(userid);
	}
}

- Controller

@Controller
@RequestMapping(value="/member")
public class MemberController {
	
	@Autowired
	MemberService service;
	
	@RequestMapping(value="/join", method=RequestMethod.POST)
	public String doJoin(Model model, User user, BindingResult bindingResult){
		boolean noError = true;
		new JoinValidator().validate(user, bindingResult);
		if(bindingResult.hasErrors()){
			noError = false;
		} else if(service.selectUser(user.getUserid()) != null){
			noError = false;
			bindingResult.rejectValue("userid", "join.userid.duplicated");
		}
		
		if(noError){
			if(service.insertUser(user) == 0){
				return "/error/error";
			} else {
				model.addAttribute("name", user.getName());
				return "member/member_join_complete";
			}
		} else {
			user.setPassword("");
			model.addAttribute("user", user);
			model.addAttribute("interests", service.getInterests());
			return "member/member_join";
		}
	}
}

Validator 관련한 소스도 들어가서 조금 길다.. 일단 JoinValidator에서 설정한 유효성검사를 진행한 후, 에러가 없으면 getUser 서비스를 호출하여 해당 아이디가 이미 존재하는지 확인한다. 존재한다면 레코드를 가져올 것이고 존재하지 않는다면 레코드가 null이 된다.

 

- DAO

@Repository
public class MemberDaoImpl implements MemberDao {
	
	String namespace = "mappers.MemberMapper.";

    @Autowired
    SqlSessionTemplate session;
    
	@Override
	public User getUser(String userid) {
		return session.selectOne(namespace + "getUser", userid);
	}
}

위와 과정은 같지만 리스트가 아닌 하나의 레코드만 가져으므로 SessionTemplate의 메소드는 selectOne을 사용한다. 또한 userid를 파라미터로 넘겨준다.

 

- VO

public class User {
	private int userno;
	private String userid;
	private String password;
	private String name;
	private String email;
	private int point;
	private short rank;
	private Date regdate;
	private Date lastdate;
	private String gender;
	private String[] interests;
}

User 테이블의 내용은 활용하는 곳이 많아서 따로 VO를 만들어서 사용한다.

 

- Mapper

	<select id="getUser" parameterType="string" resultType="User">
		SELECT
			USERID, NAME, POINT, RANK, REGDATE, LASTDATE, GENDER
		FROM
			TB_USER
		WHERE
			USERID = #{userid}
	</select>

DAO에서 파라미터로 String인 userid를 넘겨줬으므로 parameterType에는 string(mybatis에서 java.lang.String은 string으로 사용한다.)을 적어주고 resultType으로 vo로 지정한다. 

이전 포스팅에셔 mybatis config에 sample.model.User를 User로 alias를 해줘서 resultType에 긴 클래스명대신 alias를 사용했다.

SQL문에 들어가는 파라미터는 #{파라미터}의 형태로 SQL에 넣어준다. DAO에서 넘겼을때의 파라미터명과 동일해야한다.

vo의 변수명과 컬럼명이 다르면 제대로 매핑이 안되므로 동일하게 해야한다.

 

↑ 해당 아이디 값이 있어서 리턴값이 null이 아니면 이미 사용중임을 나타낸다.

 

 

3. INSERT

 

회원정보를 TB_USER에 INSERT 시키고 난 후, userno값을 받아 회원정보창에서 선택한 관심사를 TB_USER_INTEREST에 추가한다.

 

- DB

CREATE TABLE `TB_USER_INTEREST` (
  `USER_INTEREST_ID` int(11) NOT NULL AUTO_INCREMENT,
  `USERID` varchar(30) CHARACTER SET utf8 NOT NULL,
  `INTEREST_ID` int(11) NOT NULL,
  PRIMARY KEY (`USER_INTEREST_ID`),
  KEY `TB_USER_INTEREST_FK_USERID` (`USERID`),
  KEY `TB_USER_INTEREST_FK_INTEREST_ID` (`INTEREST_ID`),
  CONSTRAINT `TB_USER_INTEREST_FK_INTEREST_ID` FOREIGN KEY (`INTEREST_ID`) REFERENCES `TB_INTEREST` (`INTEREST_ID`),
  CONSTRAINT `TB_USER_INTEREST_FK_USERID` FOREIGN KEY (`USERID`) REFERENCES `TB_USER` (`USERID`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

TB_USER는 위와 같이 구성되어있다.

 

@Service
public class MemberServiceImpl implements MemberService {

	@Autowired
	MemberDao dao;
	
	@Transactional
	public Integer insertUser(User user) {
		user.setUserno(dao.insertUser(user));
		return dao.insertUserInterest(user);
	}
}

Controller는 2번과 같으므로 생략한다. 다만 POST로 받은 파라미터의 값이 User Vo에 담겨져 Service로 전달된다. 

먼저 TB_USER 테이블에 데이터를 INSERT한 뒤 마지막으로 AUTO INCREAMENT된 userno를 UserVo에 담아 TB_USER_INTEREST에 관심사 정보를 INSERT한다.

또한, 두 번의 쿼리실행은 둘다 성공하거나 둘다 실패해야하므로 @Transactional 어노테이션을 사용하여 트랜잭션 처리를 한다.

 

 

- DAO

@Repository
public class MemberDaoImpl implements MemberDao {
	
	String namespace = "mappers.MemberMapper.";

    @Autowired
    SqlSessionTemplate session;
    
	@Override
	public int insertUser(User user) {
		return session.insert(namespace + "insertUserInfo", user);
	}

	@Override
	public int insertUserInterest(User user) {
		ArrayList<Map<String, String>> list = new ArrayList<Map<String, String>>();
		for(String interest : user.getInterests()){
			Map<String, String> paramMap = new HashMap<String, String>();
			paramMap.put("userid", user.getUserid());
			paramMap.put("interest", interest);
			list.add(paramMap);
		}
		return session.insert(namespace + "insertUserInterest", list);
	}
}

insert는 SessionTemplate의 insert 메소드를 사용한다. 

 

① TB_USER 

파라미터로 사용자 데이터가 담긴 User VO를 넘기는데 리턴타입이 int인 이유는 일반적으로는 mybatis에서 insert가 성공적으로 처리되면 1, 실패하면 0으로 반환하지만 이 INSERT문은 SELECT KEY로 불러오는 마지막 userno의 타입이 Integer이기 때문이다.

 

② TB_USER_INTEREST

최종적으로 INSERT가 되면 다음과 같이 입력이 되는데, USERID : INTEREST_ID가 1:N관계가 되므로 리스트 맵형태로 만들여 파라미터를 넘긴다.

 

 

- Mapper

<insert id="insertUserInfo" parameterType="User">
		INSERT INTO TB_USER(USERID, PASSWORD, NAME, EMAIL, GENDER)
		VALUES(#{userid}, #{password}, #{name}, #{email}, #{gender})
		<selectKey keyProperty="userno" resultType="int" order="AFTER">
			SELECT LAST_INSERT_ID()
		</selectKey>
</insert>

INSERT에 필요한 파라미터를 Vo객체로 받았고, VO의 변수명과 동일하게 하여 파라미터를 할당한다. key값인 userno는 autoincreament속성을 가지고 있어서 자동으로 부여 되고 point, rank, regdate, lastdate는 미리 설정한 기본값으로 테이블에 추가된다.

 

또한, SELECT KEY는 INSERT 직전(Before) 혹은 직후(AFTER)에 해당 테이블(TB_USER)의 지정한 마지막 primary key값(keyProperty, resultType)을 가져온다.

DB종류마다 마지막 값을 가져오는 방법이 다르므로 Oracle이나 MS-SQL은 여기를 참고하자.

<insert id="insertUserInterest">
		INSERT INTO TB_USER_INTEREST(USERID, INTEREST_ID)
		VALUES	<foreach collection="list" item="item" separator=" , ">
		            (#{item.userid}, #{item.interest})
		        </foreach>
</insert>

INSERT자체는 TB_USER에 추가할때와 크게 다를 것은 없지만 다중 INSERT를 위해 foreach를 사용한다.

 

<foreach> 태그 속성 설명

collection : parameterType으로 넘어온 map안에 list(map에 key값)

item : collection을 사용할 변수 명

seperator : 반복 문자열을 구분할 문자

 

참고 - http://blog.woniper.net/194

 

 

뭔가 두서없이 적었다...

'Programming > >> Spring' 카테고리의 다른 글

[Spring] log4sql SQL로그 확인하기  (0) 2016.03.02
[Spring] Could not write JSON: Object is null  (0) 2016.02.26
[Spring] Spring Mybatis 설정 1  (2) 2016.02.17
[Spring] Spring Validator  (1) 2015.12.02
[Spring] Spring 다국어 지원  (0) 2015.12.02

댓글