본문 바로가기

Mybatis & Ajax

[211222] Mybatis (로그인)

MyBatis란?

- 자바의 관계형 데이터 베이스 프로그래밍을 보다 쉽게 도와주는 프레임워크

 

MyBatis를 왜 사용할까?

MyBatis는 흔히 SQL 매핑 프레임워크로 분류되는데, 개발자들은 JDBC 코드의 복잡하고 지루한 작업을 피하는 용도로 많이 사용한다. 즉, JDBC를 보다 편하게 사용하기 위해 개발되었다.

 

전통적인 JDBC와 MyBatis를 비교해보자.

전통적인 JDBC MyBatis
- 직접 Connection을 맺고 마지막에 close()
- PreparedStatement 직접 생성 및 처리
- PreparedStatement의 setXXX() 등에 대한 모든 작업을 개발자가 처리
- SELECT의 경우 직접 ResultSet 처리
- 자동으로 Connection close() 가능
- MyBatis 내부적으로 PreparedStatement 처리
- #{prop}와 같이 속성을 지정하면 내부적으로 자동 처리
- 리턴 타입을 지정하는 경우 자동으로 객체 생성 및 ResultSet 처리

- MyBatis는 기존의 SQL을 그대로 활용할 수 있고, 진입장벽이 낮은 편이어서 JDBC의 대안으로 많이 사용한다.

- 스프링 프레임워크의 특징 중 하나는 다른 프레임워크들과의 연동을 쉽게 하는 추가적인 라이브러리들이 많다는 것인데, MyBatis 역시 mybatis-spring이라는 라이브러리를 통해 쉽게 연동 작업을 처리할 수 있다.

 

https://sw-engineering.tistory.com/32

 

Mybatis 설치

 

mybatis 검색 > 라이브러리 다운 

 

 

 

 

압축 푼 후 해당파일 프로젝트폴더 > src > ... 경로 안의 lib 폴더에 넣기 

전제 조건 - JRE Sysyem Library 

전제조건

 

연결

1. MyBatisConnect.java 작성

package com.db.conn;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MybatisConnect {
	private SqlSession sess;
	
	public MybatisConnect() {
		this.connect();
	}
	private void connect() {
		//마이바티스 구성(mybatis-config.xml)을 사용하여 연결 객체(SqlSession)를 만들기 위한 코드 작성
		//xml 파일 별도로 만들어 주어야 함
		//ㄴsrc - main - java 안에 resources 로 패키지 생성 후 파일 작성
		
		String resource = "resources/mybatis-config.xml"; //구성이 있는 경로
		InputStream inputStream;
		try {
			inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
			this.sess = sqlSessionFactory.openSession(false); //false = 오토커밋 하지 않겠다
			// 오라클 커넥트의 this.conn.setAutoCommit(false)와 같은 개념
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public SqlSession getSession() {
		//사용 후 세션 반환 해 주어야 함
		if(this.sess == null) {
			this.connect();
		}
		return this.sess;
	}
	
	public void commit() {
		this.sess.commit();
	}
	
	public void rollback() {
		this.sess.rollback();
	}
	
	public void close() {
		this.sess.close();
	}
}

 

 

2. 패키지 안에 xml 파일 생성 (mybatis-config.xml) 

마이바티스 사이트의 시작하기 > 1번 내용 붙여 넣기

 

 

3. 오라클 프로퍼티 사용하도록 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>
  <properties url="file:///C:/users/lmry/oracle_connect.prop"> <!-- 프로퍼티 파일 경로 -->
  </properties>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="oracle.jdbc.driver.OracleDriver"/> <!-- 오라클 사용하도록 수정 -->
        <property name="url" value="${cloud-url}"/> <!-- 월렛 --> <!-- EL, 프로퍼티에 사용되는 변수 값 출력해라 라는 뜻 -->
        <property name="username" value="${user}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="resources/mapper/account.xml"/> <!-- 패키지, 매퍼 파일 만들어주기 -->
  </mappers>
</configuration>

  <mappers>
    <mapper resource="resources/mapper/account.xml"/> <!-- 패키지, 매퍼 파일 만들어주기 -->
  </mappers>

(프로젝트 폴더 안의 src/main/java/ 경로는 생략(예전에 편의를 위해 root 설정 해주었기 때문?)

 

 

4. account.xml 파일 생성 후 구문 붙여넣기

(향후 수정할 예정)

<?xml version="1.0" encoding="UTF-8" ?>
http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper"> 
  <select id="selectBlog" resultType="Blog"> <!--resultType : 나중에 내가 지정한 타입(DTO)으로 반환될 것-->
    select * from Blog where id = #{id} <!--변수값-->
  </select>
</mapper>

매퍼 Map = 지도 - sql 쿼리 찾는다.

mapper의 namespace + select의 id로 찾게 될 것


테스트

 

OracleConnection 을 이용해 데이터를 가져오던 Main의 top_navigation 부분을 MybatisConnetion 으로 가져오기

(연결 방식만 달라지는 것)

 

1. mybatis-config.xml 의 매퍼 태그에 테스트용 main.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>
  <properties url="file:///C:/users/lmry/oracle_connection.prop"> <!-- 프로퍼티 파일 경로 -->
  </properties>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="oracle.jdbc.driver.OracleDriver"/> <!-- 오라클 사용하도록 수정 -->
        <property name="url" value="${cloud-url}"/> <!-- 월렛 --> <!-- EL, 프로퍼티에 사용되는 변수 값 출력해라 라는 뜻 -->
        <property name="username" value="${user}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="resources/mapper/main.xml"/>
    <mapper resource="resources/mapper/account.xml"/> <!-- 패키지, 매퍼 파일 만들어주기 -->
  </mappers>
</configuration>

 

2. main.xml 파일 생성 후 구문 작성

 1) 매퍼의 namespace 수정

 2) select 태그 id 수정, resultType 수정 (적는대로(DTO) 반환 됨)

 3) db에 저장되어 있는 테이블 정보 가져오는 쿼리 작성. 이미 MainDAO에 작성되어서 oc로 가져와지는 쿼리문 그대로  사용하면 됨, ORDER BY 로 정렬만 해 줌

-> 정렬한 순서대로 main 페이지의 top-navigation 홈, 방명록, 게시판이 홈, 게시판, 방명록 순으로 출력 됨

 

필드명

이 때 테이블의 컬럼명과 반환타입 DTO의 필드명이 동일해야 한다.

다를 경우 조회는 되지만 매핑이 되지 않아 NULL 값이 나온다.

 

-> 쿼리문 별칭 사용하여 필드명에 맞춰서 작성, 대소문자 신경 x

 

 

 

<?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="MainMapper"> <!-- 이름 간단히 작성 -->
  <select id="selectMenus" resultType="com.web.main.model.NavMenuDTO">
    SELECT * FROM NAV_MENUS ORDER BY ODR
  </select>
</mapper>

 

3. OracleConnection 으로 데이터 가져오는 MainDAO 수정

(이전에는 MenuDAO였는데 이름 변경함)

package com.web.main.model;

import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.db.conn.MybatisConnect;

public class MainDAO {
	private MybatisConnect mc;
	private SqlSession sess;
	
	public MainDAO() {
		this.mc = new MybatisConnect();
		this.sess = this.mc.getSession();
	}
	
	public List<NavMenuDTO> select() {
		List<NavMenuDTO> datas = this.sess.selectList("MainMapper.selectMenus");//받은 리스트를 main.xml의 리절트 타입이 dto이기 때문에 알아서 dto로 해서 반환해줌
		
		return datas;
	}
	
	public void commit() {
		mc.commit();
	}
	
	public void rollback() {
		mc.rollback();
	}
	
	public void close() {
		mc.close();
	}
}

 

Tip

 

필드명이 컬럼명과 다른 NavMenuDTO2 에 대해 <resultMap> 다음과 같이 작성,

맨 마지막 select 태그에 엮어주면 별칭 처리 필요 없어진다. (캡쳐에 남아있는 쿼리의 별칭들 지우고 실행하면 됨)


<?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="MainMapper"> 
  <!-- 필드명/컬럼명 다를 경우에 미리 할당해두면 매번 별칭으로 지정할 필요 없음 -->
  <resultMap type="com.web.main.model.NavMenuDTO" id="menuMap">
  	<result property="ids" column="id" /> <!-- property가 컬럼과 다른 필드명 -->
  	<result property="names" column="name" /> 
  	<result property="urls" column="url" />
  	<result property="odrs" column="odr" />
  </resultMap>
  
  <select id="selectMenus" resultType="com.web.main.model.NavMenuDTO">
    SELECT ID 
    	 , NAME 
    	 , URL 
    	 , ODR 
      FROM NAV_MENUS
      ORDER BY ODR
  </select>
</mapper>

 

 


실습

로그인 기능 수정 (MybatisConnect 사용)

 

사용자가 /login 에서(로그인 컨트롤러의 get) 아이디와 비밀번호 입력 -> 로그인 컨트롤러의 post로 전송  

-> 로그인 컨트롤러에서 service.login(dto) 가 true면 가입한 유저정보를 session.setAttribute로 index.jsp(메인페이지) 에 넘겨주고 있음.

 

로그인 컨트롤러

 

이 때 기존처럼 유저 목록을 리스트로 받을 필요 없이 DTO로 가입한 한명의 정보만 받아오면 되기 때문에

step 1) MybatisConnect, SqlSession 연결 (AccountDAO)

 

 

 

 

 

 

mc.getSession() 메서드까지(세션 반환) 반드시 dao 생성자 호출시 실행되도록 한다.

 

 

step 2)

-> service.login() 안에서 호출되는 dao.select() 함수 수정. selectLoginAccount()

AccountDAO.java

package com.web.account.model;

import org.apache.ibatis.session.SqlSession;

import com.db.conn.MybatisConnect;
import com.db.conn.OracleConnect;

public class AccountDAO {
	private OracleConnect oc;
	private MybatisConnect mc;
	private SqlSession sess;
	
	public AccountDAO() {
		this.oc = new OracleConnect(true);
		this.mc = new MybatisConnect();
		this.sess = this.mc.getSession();
	}
	
	public boolean insert(AccountDTO dto) {
		String query = "INSERT INTO ACCOUNTS VALUES("
				+ "ACCOUNTS_SEQ.NEXTVAL, "
				+ "'" + dto.getUsername() + "', "
				+ "'" + dto.getPassword() + "', "
				+ "'" + dto.getEmail() + "', "
				+ "SYSDATE)";
		int res = oc.insert(query);
		//반환되는 rs는 추가또는 수정또는 삭제된 수량을 뜻함 
		return res == 1 ? true : false;
		//하나 추가 됐다고 나오면 true
	}
	
//  selectLoginAccount()로 수정
//	public List<AccountDTO> select(String username) {
//		String query = "SELECT * FROM ACCOUNTS WHERE USERNAME = '" + username + "'";
//		ResultSet res = oc.select(query);
//		
//		List<AccountDTO> datas = new ArrayList<AccountDTO>();
//		try {
//			while(res.next()) {
//				AccountDTO dto = new AccountDTO();
//				//테이블 만든 컬럼명column label 그대로 사용- res.getStirng 매개변수 체크
//				dto.setId(res.getInt("ID"));
//				dto.setUsername(res.getString("USERNAME"));
//				dto.setPassword(res.getString("PASSWORD"));
//				dto.setEmail(res.getString("EMAIL"));
//				dto.setJoinDate(res.getDate("JOINDATE"));
//				datas.add(dto);
//			}
//		} catch (SQLException e) {
//			e.printStackTrace();
//		}
//		return datas;
//	} 
	
	public AccountDTO selectLoginAccount(String username, String password){
		AccountDTO dto = new AccountDTO();
		dto.setUsername(username);
		dto.setPassword(password);
		//객체에 담아서 파라미터로 account.xml로 보낸다.
        //파라미터 하나밖에 못 싣기 때문에 DTO 객체로 묶어 보낸다. 이때 account.xml 파라미터타입 해당 dto로 지정해야 함(com.web.---)
		AccountDTO data = this.sess.selectOne("AccountMapper.selectAccount", dto); 
									//account.xml의 mapper namespace & <select>의 id
		System.out.println("AccountDAO.selectLoginAccount() ->" + data); //login 가서 로그인 했을 때 유저정보 나와야 로그인 정상
		return data;
	}

	public void commit() {
		oc.commit();
		mc.commit();		
	}
	
	public void rollback() {
		oc.rollback();
		mc.rollback();
	}
	
	public void close() {
		oc.close();
		mc.close();
	}
}

 

AccountService.java

service.login() 메소드 수정

package com.web.account.model;

import java.util.List;

public class AccountService {

	public boolean isValid(AccountDTO dto) {
		if(isEmpty(dto.getUsername()) || isEmpty(dto.getPassword()) 
				|| isEmpty(dto.getEmail())) {
			return false; //하나라도 비어있으면 false 반환. 
		}
		return true;
	}

	private boolean isEmpty(String str) {
		return str.isEmpty();
	}
//dao.select() 함수 -> dao.selectLoginAccount() 변경하면서 반환타입도 dto로 변경하였기 때문에 우선 주석 처리
	public boolean add(AccountDTO dto) {
//		AccountDAO dao = new AccountDAO();
//		//중복만 보면 되니까 dto 전부를 넘길필요는 없음.
//		int count = dao.select(dto.getUsername()).size();//반환되는 리스트의 사이즈
//		
//		if(count == 0) {//중복체크하고(이미있는데이터가아니면) 
//			boolean res = dao.insert(dto);//가입안한 회원이면 가입해줌, 추가
//			if(res) {
//				dao.commit();
//				dao.close();
//				return true;
//			} else {
//				dao.rollback();
//				dao.close();
//				//롤백 굳이 할 필요없음 이미 if문으로 유효성검사해서 추가되는 것이므로
//				return false;
//			}
//		} else {
			return false;
//		}
	}
	
	public boolean login(AccountDTO dto) { //로그인하기, 비밀번호 동일해야 됨
		AccountDAO dao = new AccountDAO();
		AccountDTO data = dao.selectLoginAccount(dto.getUsername(), dto.getPassword());
		dao.close();
		if(data != null) {
			if(dto.getPassword().equals(data.getPassword())) {
				dto.setId(data.getId());
				dto.setPassword(""); //보안을 위해 패스워드 일치 판단만 하고 저장하지 않음
				dto.setEmail(data.getEmail());
				dto.setJoinDate(data.getJoinDate());
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
}

 

account.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">
  <!-- 넘어오는 파라메터(AccountDTO select() - username)의 타입을 지정해주어야 한다.  -->
<mapper namespace="AccountMapper">
  <select id="selectAccount" parameterType="com.web.account.model.AccountDTO" resultType="com.web.account.model.AccountDTO"> 
  	SELECT * FROM ACCOUNTS WHERE USERNAME = #{username} AND PASSWORD = #{password}<!--AccountDAO.selectLoginAccount()  -->
  </select> 							<!-- dto로 넘겼을 경우 #{}변수가 String type이면 mybatis-config.xml 에서처럼 필드명 입력, 대소문자 구별해야 함-->
</mapper>