JDBC / MyBatis

Cheol·2023년 6월 23일

edu day 29, 30

추가 / 수정

23/06/23 - MyBatis 개념, 환경, 코드
23/06/26 - Exception, List에 Map담기
23/06/27 - 주의 문법

🤨What is MyBaits?

앞서 배운 statememt, preparedstatement 는 oracle과 연동하기 위해 Connection, Statement, ResultSet 등을 사용하면서 코드를 구현하였다.
그러나 이러한 코드는 변수 또는 중복이 많이 생겨서 추상화해도 깔끔하지 않은 코드로 보인다.

더 깔끔하고 나은 방법으로 구현 하기 위해 MyBatis를 사용하게 되는 것이다.😁
MyBatis는 객체 지향 언어의 자바의 JDBC를 이용한 퍼시스턴스 프레임워크이다.

다만 단점이 있다면, 맨 처음 환경을 구현하는 것이 복잡하다. 처음만 제대로 구현해 놓으면 가독성 좋은 메서드를 활용하여 깔끔하고 간결하게 코드를 구현할 수 있다.


👌MyBatis특징

  • JDBC를 이용한 커넥션 코드 및 변수 등 중복 작업을 대체해준다.
  • SQL, 동적 쿼리, 저장 프로시저 그리고 고급 매핑을 지원하는 SQL Mapper이다.
  • SQL 쿼리들을 따로 XML파일로 작성하여 프로그램 코드를 간결화 하기 용이하다.

❗Exception 주의❗

  • DAO에서 호출한 id와 mapper에서 설정된 id가 다를 경우
  • mapper에서 설정한 id 중 중복된 id가 있을 경우

🔎MyBatis 환경 만들기 / 코드구현(SelectAll, SelectOne, Insert, Update, Delete)

  • 😋jdbc.properties ( make in src.folder )
#key = value
oracle.jdbc=oracle.jdbc.driver.OracleDriver
oracle.url=jdbc:oracle:thin:@localhost:1521:xe
oracle.userid=scott
oracle.passwd=tiger

mysql.jdbc=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql:localhost:3306\orcl
mysql.userid=scott
mysql.passwd=tigerc

  • 😮Configuration.xml ( make in src.folder )
<?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>
	<!-- jdbc.properties파일 등록 -->
<properties resource="jdbc.properties"/>

<environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <!-- db 연결 4가지 정보  -->
      <dataSource type="POOLED">
        <property name="driver" value="${oracle.jdbc}"/>
        <property name="url" value="${oracle.url}"/>
        <property name="username" value="${oracle.userid}"/>
        <property name="password" value="${oracle.passwd}"/>
<!--    <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>-->
      </dataSource>
    </environment>
  </environments>
 	<!-- Mapper.xml의 경로 등록 (Mapper.xml파일이 존재해야함 ) -->
  <mappers>
	  <mapper resource = "DeptMapper.xml"/>
  </mappers>
</configuration>

  • 😁DeptMapper.xml ( make in src.folder )
<?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="org.mybatis.example.BlogMapper">

<select id="selectAll" resultType="com.dto.Dept">
	select * from dept
</select>

<insert id="deptInsert" parameterType="com.dto.Dept">
	insert into dept values(#{deptno}, #{dname}, #{loc})
</insert>

<update id="deptUpdate" parameterType="com.dto.Dept">
	update dept set dname=#{dname}, loc=#{loc} where deptno=#{deptno}
</update>

<delete id="deptDelete" parameterType="int">
	delete from dept where deptno=#{i}
</delete>

<select id="selectByDeptno" parameterType="int" resultType="com.dto.Dept">
	select deptno, dname, loc from dept where deptno=#{deptno}
</select>

</mapper>

  • 😋Dept.java
package com.dto;

public class Dept {
	 int deptno;
	 String dname;
	 String loc;
	
	 public Dept() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Dept(int deptno, String dname, String loc) {
		super();
		this.deptno = deptno;
		this.dname = dname;
		this.loc = loc;
	}

	@Override
	public String toString() {
		return "Dept [deptno=" + deptno + ", dname=" + dname + ", loc=" + loc + "]";
	}

	public int getDeptno() {
		return deptno;
	}

	public void setDeptno(int deptno) {
		this.deptno = deptno;
	}

	public String getDname() {
		return dname;
	}

	public void setDname(String dname) {
		this.dname = dname;
	}

	public String getLoc() {
		return loc;
	}

	public void setLoc(String loc) {
		this.loc = loc;
	}
	    
}

  • 😮MySqlSessionFactory.java
package com.config;

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 MySqlSessionFactory {
	
	static SqlSessionFactory sqlSessionFactory= null;
	static {//초기화
		String resource = "Configuration.xml";  //수정이 필요한 유일한 부분 
		//Configuration.xml의 경로가 바뀔시에 수정이 필요 
		InputStream inputStream= null;
		try {
			inputStream = Resources.getResourceAsStream(resource);
			System.out.println("configuration.xml 로딩 성공 ");
		} catch (IOException e) 
		{
		System.out.println("configuration.xml 로딩성공ㄴ");
			e.printStackTrace();
		}
		 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		 //DriverManager와 비슷한 객체 
	}//end static
	
	//SqlSession 반환
	public static SqlSession getSqlSession() {//
		//    SqlSession session=    MySqlSessionFactory.getSqlSession();
		SqlSession session = sqlSessionFactory.openSession();
		//getConnection의 Connection과 비슷한 기능의 SqlSession객체 생성 리턴 
		return session;
	}
}

📌DeptMapper.xml 에서 mapper로 설정해준 select, insert, update, delete 들은 제외하고, 여기까지 코드는 기본으로 구현해야 Oracle과 연동을 할 수 있다.

앞서 배운 Connection대신, 위 코드에서 만든 메서드 SqlSession을 이용하여 로딩한다.


  • 😁OracleMyBatisMain.java
import java.util.List;

import com.dto.Dept;
import com.service.OracleMyBatisService;

public class OralceMyBatisMain {

	public static void main(String[] args) {

		OracleMyBatisService service = new OracleMyBatisService();
		service.insert(new Dept(99, "개발", "강남"));
		service.update(new Dept(99, "영업", "서울"));
		service.delete(99);
		Dept dept = service.selectByDeptno(99);
		System.out.println(dept);
		
		//selectAll
		List<Dept> list = service.selectAll();
		for (Dept d: list) {
			System.out.println(d);
		}
	}
}

  • 😋OracleMyBatisService.java
package com.service;

import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.config.MySqlSessionFactory;
import com.dao.OracleMyBatisDAO;
import com.dto.Dept;

public class OracleMyBatisService {

	OracleMyBatisDAO dao;
	
	public OracleMyBatisService() {
		super();
		// TODO Auto-generated constructor stub
		dao = new OracleMyBatisDAO();
	}

	public List<Dept> selectAll() {
		List<Dept> list = null;
		SqlSession session = MySqlSessionFactory.getSqlSession();
		list = dao.selectAll(session);
		return list;
	}

	public void insert(Dept dept) {
		// TODO Auto-generated method stub
		SqlSession session = MySqlSessionFactory.getSqlSession();
		try {
			dao.insert(session, dept);
			session.commit();
		} finally {
			session.close();
		}
	}

	public void update(Dept dept) {
		// TODO Auto-generated method stub
		SqlSession session = MySqlSessionFactory.getSqlSession();
		try {
			dao.update(session, dept);
			session.commit();
		} finally {
			session.close();
		}
	}

	public void delete(int i) {
		SqlSession session = MySqlSessionFactory.getSqlSession();
		try {
			dao.delete(session, i);
			session.commit();
		} finally {
			session.close();
		}
		
	}

	public Dept selectByDeptno(int i) {
		SqlSession session = MySqlSessionFactory.getSqlSession();
		Dept dept = dao.selectByDeptno(session, i);
		return dept;
	}

}

  • 😮OracleMyBatisDAO.java
package com.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.dto.Dept;

public class OracleMyBatisDAO {

	public List<Dept> selectAll(SqlSession session) {
		List<Dept> list = session.selectList("selectAll");
		return list;
	}

	public void insert(SqlSession session, Dept dept) {
		session.insert("deptInsert", dept);
	}

	public void update(SqlSession session, Dept dept) {
		session.update("deptUpdate", dept);
	}

	public void delete(SqlSession session, int i) {
		session.delete("deptDelete", i);
	}

	public Dept selectByDeptno(SqlSession session, int deptno) {
		Dept dept = session.selectOne("selectByDeptno",deptno);
		return dept;
	}

}

✨Mapper 사용 순서 / 주의점

  • Mapper.xml 파일 내부의 <mapper> </mapper> 태그 사이에 사용할 SQL문을 작성한다.
  • 사용할 DQL, DML을 <select> </select> 형식으로 작성한다.
  • 만약 사용자가 지정하고 싶은 SELECT문이 여러 개 라면, 분류하기 위해 sql마다 id를 지정해주고 resultTypeparameterType를 지정해준다. 그 후 <select> </select> 태그 내부에 sql문을 작성해준다.
  • parameterType으로 값을 가져온 변수 또는 클래스는 #{변수} 형식으로 받아준다.
    이 변수를 받는 이름은 SqlSession 객체의 메서드를 선언한 함수에서 지정한 변수의 이름이다.
  • ❗주의할 점은 sql문을 작성할 때 ;(세미콜론)은 제외시킨다.

    📌형식 1

    • <select id="selectByDeptno" parameterType="int" resultType="com.dto.Dept">
      select deptno, dname, loc from dept where deptno=#{deptno}
      </select>


      📌형식 2
    • <update id="deptUpdate" parameterType="com.dto.Dept">
      update dept set dname=#{dname}, loc=#{loc} where deptno=#{deptno}
      </update>

❗앞서 설명한 Mapper의 id와 호출한 메서드에 넣은 id를 다르게 작성하여 Exception상황이 발생하지 않게 주의하자!!


❗❗Exception 종류

  • configuration.xml에 매퍼 지정이 제대로 안되었거나 DAO, Mapper에서 이름이 틀렸을 때, 쿼리문 id가 일치하지 않을 때 등 Exception 발생

    Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.dept.DeptMapper2.selectAll ### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.dept.DeptMapper2.selectAll
  • query문 실행 코드에 오류가 있을 때

    Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException:
    ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: ORA-00936: missing expression

  • DeptMapper.xml의 쿼리문에서 resultType, parameterType 등에 오류가 있을 때

    Exception in thread "main" java.lang.ExceptionInInitializerError
    at com.service.OracleMyBatisService.selectAll(
    OracleMyBatisService.java:22)
    at OracleMyBatisMain3.main(OracleMyBatisMain3.java:11)
    Caused by: org.apache.ibatis.exceptions.PersistenceException:
    ### Error building SqlSession.
    ### The error may exist in DeptMapper2.xml

  • 반환 되는 값 또는 출력하는 값이 null값인 경우 NullPointerException 발생.
    && configuration.xml의 DB연결 정보(4가지)가 맞지 않을 때도 널포인터예외 발생.
    && DeptMapper.xml의 쿼리문 실행 코드와 parameterType이 일치하지 않을 경우도 ❗NullPointerException이 발생한다.

    Exception in thread "main" java.lang.NullPointerException : Cannot invoke "java.util.List.iterator()" because "list" is null
    at OracleMyBatisMain3_1.main
    (OracleMyBatisMain3_1.java:17)




🤨List에 <HashMap> 형식의 데이터 담기

  • Main.java
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import com.service.OracleMyBatisService;

public class OracleMyBatisMain4_1 {

	public static void main(String[] args) {

		OracleMyBatisService service = new OracleMyBatisService();
		
		List<HashMap> list = service.selectAllHashmap();
		for (int i = 0; i < list.size(); i++) {
			String dname = (String)list.get(i).get("DNAME");
			String loc = (String)list.get(i).get("LOC");
			int deptno = ((BigDecimal)list.get(i).get("DEPTNO")).intValue();
			System.out.println(deptno +"\t" + dname + "\t" + loc);
		}

		for (HashMap Map : list) {
			Set <String> keys = Map.keySet();
			for (String key : keys) {
				System.out.print(Map.get(key) + "\t");
			}
			System.out.println();
		}
		
	}

}
  • Service.java
package com.service;

import java.util.HashMap;
import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.config.MySqlSessionFactory;
import com.dao.OracleMyBatisDAO;
import com.dto.Dept;

public class OracleMyBatisService {

	OracleMyBatisDAO dao;
	
	public OracleMyBatisService() {
		super();
		dao = new OracleMyBatisDAO();
	}
    
	public List<HashMap> selectAllHashmap() {
		SqlSession session = MySqlSessionFactory.getSqlSession();
		List<HashMap> list = null;
		try {
			list = dao.selectAllHashmap(session);
		} finally {
			session.close();
		}
		
		return list;
	}
}    
  • DAO.java
package com.dao;

import java.util.HashMap;
import java.util.List;

import org.apache.ibatis.session.SqlSession;

import com.dto.Dept;

public class OracleMyBatisDAO {

	public List<HashMap> selectAllHashmap(SqlSession session) {
		List<HashMap> list = session.selectList("selectAllHashmap");
		return list;
	}
  • Mapper.xml
<select id="selectAllHashmap" resultType="hashmap">
	select deptno, dname, loc from dept order by 1
</select>

Mybatis 주의 문법

  • <foreach> </foreach>
    list, set, map처럼 여러 개의 인자를 받을 경우 반복문을 돌려 인자를 받을 수 있다.
    item이 하나의 인자고 collection이 받은 전체의 인자이다.
    openclose로 시작과 끝을 나타내고 구분자 separator를 지정해 인자를 구분지어 sql문을 완성할 수 있다.
	<update id="multiUpdate" parameterType="arraylist">
		update dept set loc ='제주' where deptno in
		<foreach item="item" collection="list" open="(" separator="," close=")">
			#{item}
		</foreach>
	</update>

  • 리스트에 저장된 객체의 멤버변수 접근
    클래스 객체가 여러 개 들어있는 list를 인자로 받을 경우 객체의 멤버변수에 접근하려면 <foreach> 내부에 클래스 객체 전체를 받는 item에서 클래스에서 선언한 멤버변수의 이름으로 접근하면 된다.
	<delete id="multiDelete" parameterType="arraylist">
		 delete from dept where deptno in
		<foreach item="item" collection="list" open="(" separator="," close=")">
			#{item.deptno}
		</foreach>
	</delete>

  • 부등호
    MyBatis에서는 부등호를 직접 사용할 수 없다.
    • 이유는 MyBatis는 SQL 인젝션 공격 방지를 위한 매개변수 바인딩을 사용하는데 이 매개변수 바인딩은 입력 값을 안전하게 처리하여 SQL쿼리를 구성하는 데 사용된다.
      그러나 부등호를 사용하면 쿼리의 구조를 변경시킬 수 있기 때문에 SQL인젝션 공격에 취약해질 수 있다.
    • 또한 MyBatis는 다양한 데이터베이스 벤더와 호환되는 ORM(Object-Relational Mapping) 프레임워크이다. SQL 문법은 데이터베이스 벤더마다 조금씩 다를 수 있으며, 이러한 차이를 추상화하여 표준화된 방식으로 SQL을 작성할 수 있게 한다.
      MyBatis는 DB에 독립적인 SQL을 작성하는 데 초점을 맞추고 있으며, 부등호를 사용하는 것은 이 목표에 부합하지 않다.

그래서 부등호는 <![CDATA[>]> 태그를 사용하거나 &lt; 또는 &gt;를 사용하여 부등호를 작성한다.

	<select id="selectAll2" resultType="com.dto.Dept">
		select * from dept where deptno &lt; 10
	</select>

	<select id="selectAll3" resultType="com.dto.Dept">
		select * from dept where deptno <![CDATA[>]]> 10 
	</select>

0개의 댓글