package com.example.demo.dao;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import com.util.MyBatisCommonFactory;
public class TestDao extends JFrame{
SqlSessionFactory sqlSessionFactory = null;
SqlSession sqlSession = null;
public TestDao() {
//메소드 호출을 통해서 객체를 주입받는 코드가 싱글톤 패턴에서 자주 등장하는 방식이다.
sqlSessionFactory = MyBatisCommonFactory.getSqlSessionFactory();
}
public void currentTime() {
System.out.println("currentTime 호출");
String time = null;
try {
sqlSession = sqlSessionFactory.openSession();
time = sqlSession.selectOne("currentTime");
System.out.println(time);
} catch (Exception e) {
e.printStackTrace();
}
}
public void procEmpcursor() {
Map<String,Object> pMap = new HashMap<>();
try {
sqlSession = sqlSessionFactory.openSession();
sqlSession.selectOne("proc_empcursor", pMap);
System.out.println(pMap);
} catch (Exception e) {
e.printStackTrace();
}
}
//개발팀
//공통팀 - 고급정보 - 간부회의
public void procLogin1() {
Map<String,Object> pMap = new HashMap<>();
try {
pMap.put("m_id", "apple");
pMap.put("m_pw", "123777777");
sqlSession = sqlSessionFactory.openSession();
sqlSession.selectOne("proc_login1", pMap);
System.out.println(pMap);
//System.out.println(pMap.get("key"));
Object keys[] = pMap.keySet().toArray();
for(int i=0;i<keys.length;i++) {
if("r_msg".equals(keys[i])) {
JOptionPane.showMessageDialog(this, pMap.get("r_msg"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestDao td = new TestDao();
//td.currentTime();
td.procEampcursor();
//td.procLogin1();
}
}
마이바티스랑 연동하는 부분인데 main부터 먼저 살펴보자.
TestDao td = new TestDao(); 하는 순간 생성자가 호출된다.
그 이전에 전역변수를 초기화 해야된다.
SqlSessionFactory sqlSessionFactory = null;
SqlSession sqlSession = null;
해당 전역변수를 초기화한다.
자, 그렇다면 이 부분에 대한 SqlSessionFactory가 뭘까?
MyBatis API
package com.util;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyBatisCommonFactory {
Logger logger = LoggerFactory.getLogger(MyBatisCommonFactory.class);
public static SqlSessionFactory sqlSessionFactory = null;
public static void init() {
try {
String resource = "com/mybatis/MapperConfig.xml";
Reader reader = null;
reader = Resources.getResourceAsReader(resource);
if(sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, "development");
}
} catch (Exception e) {
e.printStackTrace();
}
}/////////////end of init()
public static SqlSessionFactory getSqlSessionFactory() {
init();
return sqlSessionFactory;
}
}
자바에서 Logger 사용 시 LoggerFactory로 Logger를 사용하는 방법과 lombok를 쓸 경우 @Slf4j 어노테이션을 넣고 Logger를 사용할 수 있는데,
먼저 LoggerFactory를 사용하는 경우에는
Logger log = LoggerFactory.getLogger(class명.class)
이렇게 사용해주면 되고 lombok을 사용할 경우에는 훨씬 간단하게 클래스명 위에 @Slf4j 어노테이션을 입력한 후
log.메소드명(..);
이런 식으로 사용해주면 된다(출처)
Logger logger = LoggerFactory.getLogger(MyBatisCommonFactory.class);
=====================================================================
Logger log = LoggerFactory.getLogger(class명.class)
를 통해 Logger를 등록할 수 있다.
이 부분에 해당하는 클래스를 넣었다. 다음엔
public static SqlSessionFactory sqlSessionFactory = null;
SqlSessionFactory 타입의 참조 변수를 sqlSessionFactory null값으로 선언했고, 이 값은 getSqlSessionFactory 메소드를 통해 주입받는다.(외부 클래스에서 / static으로 선언한 이유는 (미리 로딩 되어있어야해서)
public static void init() {
try {
String resource = "com/mybatis/MapperConfig.xml";
Reader reader = null;
reader = Resources.getResourceAsReader(resource);
if(sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, "development");
}
} catch (Exception e) {
e.printStackTrace();
}
}/////////////end of init()
이 경로를 resource 변수에 담는다.
왜 굳이 Reader?
Resources.getResourceAsReader(resource); 사용하기 위해서
// src/main/resources 경로의 파일읽기
Reader reader = Resources.getResourceAsReader("config_properties.properties");
reader에 해당 경로를 넣는다. sqlSessionFactory 가 null값이라면 sqlSessionFactory 를 생성해야하는 Builder가 sqlSessionFactory를 생성해야된다.
if(sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, "development");
}
public static SqlSessionFactory getSqlSessionFactory() {
init();
return sqlSessionFactory;
}
외부 클래스에서 static이기 떄문에 클래스.메소드멍() 으로 호출한다면 해당 sqlSessionFactory 객체를 불러올 수 있다.
근데 왜 init을 저 자리에 뒀을까?
시점의 문제이기 때문에 저기에 주지 않으면 생성된 sqlSessionFactory 인스턴스를 찾아 볼 수 없다. 그렇기 떄문에 저 자리에 init()이 들어가야된다.
자세히 보면 인스턴스 생성은 init() 메소드 안에서 이뤄졌다. 그렇기 때문에 꼭 호출해야된다.
그 생성된 인스턴스를 가지고 TestDao에서 어떻게 활용하는지 살펴보자.
다시 앞으로 돌아와서
public class TestDao extends JFrame{
SqlSessionFactory sqlSessionFactory = null;
SqlSession sqlSession = null;
public TestDao() {
//메소드 호출을 통해서 객체를 주입받는 코드가 싱글톤 패턴에서 자주 등장하는 방식이다.
sqlSessionFactory = MyBatisCommonFactory.getSqlSessionFactory();
}
public void currentTime() {
System.out.println("currentTime 호출");
String time = null;
try {
sqlSession = sqlSessionFactory.openSession();
time = sqlSession.selectOne("currentTime");
System.out.println(time);
} catch (Exception e) {
e.printStackTrace();
}
}
sqlSession 이란?
myBatis - SqlSession: SQL 실행
openSession(): SqlSession 얻기
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSessionFactory에서 경로를 읽어왔기 떄문에 밑에 같은 동작이 가능한거임
SqlSessionFactory 객체의 openSession()을 호출해서 SqlSession을 얻는다.
SqlSession은 MyBatis에서 실제 SQL 실행을 담당하는 컴포넌트이다. 즉 SQL을 실행하려면 SqlSession이 필요하며, 이 객체가 JDBC 드라이버를 사용한다.
SqlSession 클래스에서는 CRUD를 위한 다양한 메서드를 제공한다.
selectOne 메서드는 오직 하나의 객체만을 리턴해야 한다
한개 이상을 리턴하거나 null 이 리턴된다면, exception 이 발생할 것이다.
sqlSession.selectOne () 사용법
호출 API 는 여러 건을 가져오는 것이 아니라 한 건을 가져오는 것을 사용해야 합니다.
int count = sqlSession.selectOne("쿼리문 아이디", 파라미터);
public void procEmpcursor() {
Map<String,Object> pMap = new HashMap<>();
try {
sqlSession = sqlSessionFactory.openSession();
sqlSession.selectOne("proc_empcursor", pMap);
System.out.println(pMap);
} catch (Exception e) {
e.printStackTrace();
}
}
궁금증 > 그러면 pMap에 put해야 정보가 담기는데 put하지 않았는데 어떻게 그 정보가 담겨서 찍혀?
selectOne 메서드를 사용할 때, 매개변수로 전달한 쿼리의 실행 결과가 자동으로 Map에 매핑되어 반환될 수 있다.
MyBatis의 내부 동작 방식 중 하나로, 쿼리의 실행 결과를 자동으로 매핑하여 Map에 담아주는 메커니즘이다.
쿼리의 실행 결과가 한 행이고, 그 결과가 여러 개의 컬럼으로 구성되어 있다면, MyBatis는 자동으로 해당 결과를 Map에 매핑한다.
그 매핑 코드는 이와같다
<select id="proc_empcursor" parameterType="java.util.Map" statementType="CALLABLE">
{ call proc_empcursor(#{key, jdbcType=CURSOR, mode=OUT, javaType=java.sql.ResultSet, resultMap=empVO})}
</select>
- parameterType="java.util.Map"이 부분이 Map형태로 매핑 될 수 있게 설정한다. + (empVO-매핑)
proc_empcursor 쿼리가 여러 행을 반환하는 경우는?
selectList 메서드
를 사용하면 된다. 이 경우에는 각 행이 Map에 매핑되어 List로 반환된다.selectone 동작원리에 대해 살펴보자.
컬럼 이름을 Key로 사용하고 and 컬럼 값을 Value로 사용한다. 이게 무슨 말이냐면,
"SELECT id, name, age FROM employee"와 같이 세 개의 컬럼을 선택했다면, Map에는 "id", "name", "age"라는 세 개의 Key가 생성된다.
id" 컬럼의 값은 Map에서 "id"라는 Key에 대응하는 Value로 설정되고, "name" 컬럼의 값은 "name"이라는 Key에 대응하는 Value로 설정된다.
| id | name | age |
|----|--------|-----|
| 1 | Alice | 25 |
sqlSession.selectOne("proc_empcursor", pMap);
pMap = {
"id": 1,
"name": "Alice",
"age": 25
}
{}
형식으로 표현된 것은 자바의 Map 인터페이스나 그 구현체를 사용하여 데이터를 표현한 것
JSON(JavaScript Object Notation)은 {} 형식으로 표현되는 경량의 데이터 교환 형식이다.
public void procLogin1() {
Map<String,Object> pMap = new HashMap<>();
try {
pMap.put("m_id", "apple");
pMap.put("m_pw", "123777777");
sqlSession = sqlSessionFactory.openSession();
sqlSession.selectOne("proc_login1", pMap);
System.out.println(pMap);
//System.out.println(pMap.get("key"));
Object keys[] = pMap.keySet().toArray();
for(int i=0;i<keys.length;i++) {
if("r_msg".equals(keys[i])) {
JOptionPane.showMessageDialog(this, pMap.get("r_msg"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
pMap 변수를 선언했고 타입은 Map<>타입이다.
try문을 보면, pMap.put을 통해서 값을 주는 것을 볼 수 있다.
put을 한 이유는 m_id값과 m_pw값을 미리 지정해서 select 할 떄 넣기위함이다.
이제 sqlSession.selectOne("proc_login1", pMap); 이 코드에 대해 설명하겠다.
그 때, Map형태로 저장 되는 것이다.
😈그렇게 되면 덮어 씌워지는 것이 아니라, 계속 누적되어 저장되는 것이다.(중요)그 반환값은 pMap 객체에 저장된다.
Object keys[] = pMap.keySet().toArray(); 이 부분은 Map의 Key를 toArray로 변환했다. 그 배열이 keys에 저장된다.
for문을 돌면서 if문 조건을 수행하는데, r_msg.equals(keys[i]))는 현재 반복 중인 키가 "r_msg"인지 확인한다.
왜 r_msg를 기준으로 했는가?에 대한 답변
오라클 PL/SQL 프로시저는 기본적으로 함수(Function) 처럼 결과값을 Return 받을 수 없다.
하지만 OUT 변수를 사용하여 결과를 Return 받는 것처럼 보이게 할 수 있다. 근데 지금 r_msg가 출력변수이기 떄문에 해당 조건을 줬다.
sqlSession.selectOne("proc_login1", pMap);
for(int i=0;i<keys.length;i++) {
if("r_msg".equals(keys[i])) {
JOptionPane.showMessageDialog(this, pMap.get("r_msg"));
}
}
r_msg는 출력변수이다. 여기서 keys 배열은 무엇일까? sqlSession.selectOne("proc_login1", pMap);결과 정보를 pMap에 리턴하는데, 당연히 디비에 저장된 건수가 14건이니 총 14개의 key = {[vo,vo,vo * 14]}로 이루어져 있을 것이다. 반환된 결과값에 대해 담겨진
r_msg가 있다면 모달처리를 하려고 이렇게 진행 한 것이다. 당연히 리턴 시 이 부분도 포함되어 리턴된다. (why? 출력변수(out))