✨KB IT's Your Life✨ 5기 TIL 기자단 활동
KB IT's Your Life 5기에서 학습한 내용을 복습하고자 정리한 글
기간: 8/12 ~ 8/17
학습 내용: Spring & MyBatis Framework
SQL, 저장 프로시저 및 고급 매핑 기능을 지원하는 자바 기반의 persistence framework
XML 파일 및 어노테이션을 사용하여 SQL 쿼리를 작성하고, 자바 객체와 DB 테이블 간의 매핑을 처리
⇒ SQL 쿼리문을 XML 파일에 작성해두면 MyBatis 프레임워크가 자동으로 DB 연결처리 등을 지원하고 자바 객체와 매핑해준다는 얘기!

xml 설정 & DBCP 작업한 걸 config에 주입 → config를 mybatis(SqlSessionTemplate)에 적용 → mybatis가 DAO에 전달하는 흐름
Controller
Service
DAO
Mapper.java
SqlSessionFactory
mybatis-config.xml
DataSource
Mapper.xml
<select>, <insert>, <update>, <delete> 태그로 정의Mapper.java와 Mapper.xml의 SQL 쿼리 id 동일해야 매핑 가능한 점 주의!
AppConfig.java
마이바티스 싱글톤 생성 설정 담당
MyBatis도 싱글톤으로 만들어 DAO에 DI하여 사용
MyBatis는 외부 객체 = 어노테이션을 통해서 싱글톤 생성 불가능한 클래스
→ XML이나 자바 통해서 따로 싱글톤 생성 설정 필요
DBCP, myBatis, myBatis 설정만 담당해서 반복적인 작업 진행하는 팩토리 클래스 3개 존재
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import javax.sql.DataSource;
@Configuration
//@ComponentScan(basePackages = "com.multi")
public class AppConfig {
public AppConfig(){
System.out.println("AppConfig created");
}
// DB 드라이버 및 연결 설정 & DBCP 초기 설정
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/shop2?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&characterEncoding=UTF-8&useUnicode=true");
dataSource.setUsername("root");
dataSource.setPassword("1234");
dataSource.setInitialSize(5); // 초기 커넥션 수
dataSource.setMaxTotal(10); // 최대 커넥션 수
return dataSource;
}
// MyBatis 초기 설정
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
return sessionFactory.getObject();
}
// MyBatis
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}

pool = 대기줄
DB와 연결하는 객체를 요청이 들어올 때마다 생성 및 소멸시키는게 아닌, connection pool이라는 대기줄에 미리 객체 여러 개를 생성해 둔 뒤 요청이 들어오면 미리 만들어져 있는 객체를 할당하고 객체를 반납하도록 하는 식으로 동작
반납된 connection 객체는 다른 요청에서 사용 가능

board도 member 디렉토리와 동일한 구조
DAO, Mapper.java(인터페이스), Mapper.xml → 테이블 당 하나씩 생성
views의 jsp에서 member table과 관련된 요청 들어온다면
MemberController → MemberService → MemberDAO → MemberMapper.java → memberMapper.xml → DB 순서로 호출!
import com.multi.spring3.member.domain.MemberVO;
import com.multi.spring3.member.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpSession;
import java.util.List;
@Controller
@RequestMapping("/member")
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
System.out.println("MemberController created");
this.memberService = memberService;
}
@GetMapping
public String index() {
return "member/member";
}
@PostMapping("/insert")
public ModelAndView insert(MemberVO memberVO) {
System.out.println(memberVO);
String result = memberService.insert(memberVO);
System.out.println("--------->> " + result);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("result", result);
modelAndView.setViewName("member/insert_result");
return modelAndView;
}
@PostMapping("/update")
public ModelAndView update(MemberVO memberVO) {
String result = memberService.update(memberVO);
System.out.println("--------->> " + result);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("result", result);
modelAndView.setViewName("member/update_result");
return modelAndView;
}
@PostMapping("/delete")
public ModelAndView delete(@RequestParam("id") String id) {
String result = memberService.delete(id);
System.out.println("--------->> " + result);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("result", result);
modelAndView.setViewName("member/delete_result");
return modelAndView;
}
@GetMapping("/one")
public ModelAndView one(String id) {
MemberVO memberVO = memberService.one(id);
System.out.println("--------->> " + memberVO);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("memberVO", memberVO);
modelAndView.setViewName("member/one_result");
return modelAndView;
}
@GetMapping("/all")
public ModelAndView all() {
List<MemberVO> all = memberService.all();
System.out.println("--------->> " + all);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("all", all);
modelAndView.setViewName("member/all_result");
return modelAndView;
}
}
import com.multi.spring3.member.dao.MemberDAO;
import com.multi.spring3.member.domain.MemberVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MemberService {
private final MemberDAO memberDAO;
@Autowired
public MemberService(MemberDAO memberDAO) {
this.memberDAO = memberDAO;
System.out.println("MemberService created");
}
public String insert(MemberVO memberVO) {
String result = "회원가입 실패";
if (memberDAO.insert(memberVO) == 1) {
result = "회원가입 성공";
}
;
return result;
}
public String update(MemberVO memberVO) {
String result = "회원수정 실패";
if (memberDAO.update(memberVO) != 0) {
result = "회원수정 성공";
}
;
return result;
}
public String delete(String id) {
String result = "회원탈퇴 실패";
if (memberDAO.delete(id) != 0) {
result = "회원탈퇴 성공";
}
;
return result;
}
public MemberVO one(String id) {
return memberDAO.one(id);
}
public List<MemberVO> all() {
return memberDAO.all();
}
}
import com.multi.spring3.member.domain.MemberVO;
import com.multi.spring3.member.mapper.MemberMapper;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class MemberDAO {
private final SqlSessionTemplate sqlSessionTemplate;
@Autowired
public MemberDAO(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
public int insert(MemberVO memberVO) {
return sqlSessionTemplate.getMapper(MemberMapper.class).insert(memberVO);
}
public int update(MemberVO memberVO) {
return sqlSessionTemplate.getMapper(MemberMapper.class).update(memberVO);
}
public int delete(String id) {
return sqlSessionTemplate.getMapper(MemberMapper.class).delete(id);
}
public MemberVO one(String id) {
return sqlSessionTemplate.getMapper(MemberMapper.class).one(id);
}
public List<MemberVO> all() {
return sqlSessionTemplate.getMapper(MemberMapper.class).all();
}
/*
use shop2;
CREATE TABLE `member` (
`id` varchar(45) NOT NULL,
`pw` varchar(45) NOT NULL,
`name` varchar(45) DEFAULT NULL,
`tel` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='회원관리용 테이블';
desc member;
*/
}
import com.multi.spring3.member.domain.MemberVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MemberMapper {
//mapper의 id와 일치해야함
int insert(MemberVO memberVO); //<insert id="insert" ~~>
int update(MemberVO memberVO);
int delete(String id);
MemberVO one(String id); // <select id="one" ~~>
List<MemberVO> all();
}
<?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>
<typeAliases>
<package name="com.multi.spring3.member.domain"/>
<package name="com.multi.spring3.board.domain"/>
</typeAliases>
<mappers>
<mapper resource="mapper/memberMapper.xml"/>
<mapper resource="mapper/boardMapper.xml"/>
</mappers>
</configuration>
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import javax.sql.DataSource;
@Configuration
//@ComponentScan(basePackages = "com.multi")
public class AppConfig {
public AppConfig(){
System.out.println("AppConfig created");
}
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/shop2?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&characterEncoding=UTF-8&useUnicode=true");
dataSource.setUsername("root");
dataSource.setPassword("1234");
dataSource.setInitialSize(5); // 초기 커넥션 수
dataSource.setMaxTotal(10); // 최대 커넥션 수
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
return sessionFactory.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
<?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">
<!-- 인터페이스(MemberMapper.java)를 namespace로 지정해두면 unique하게 구분 가능 -->
<mapper namespace="com.multi.spring3.member.mapper.MemberMapper">
<insert id="insert"
parameterType="com.multi.spring3.member.domain.MemberVO">
insert into member
values (#{id}, #{pw}, #{name}, #{tel});
</insert>
<update id="update"
parameterType="com.multi.spring3.member.domain.MemberVO">
update member set tel = #{tel}
where id = #{id}
</update>
<delete id="delete"
parameterType="String">
delete from member
where id = #{id}
</delete>
<select id="one"
parameterType="String"
resultType="com.multi.spring3.member.domain.MemberVO">
select * from member
where id = #{id}
</select>
<select id="all"
resultType="com.multi.spring3.member.domain.MemberVO">
select * from member
</select>
</mapper>
<settings>
<!-- MyBatis가 로그 기록할 유형 결정 -->
<setting name="logImpl" value="SLF4J" />
<!-- 자주 반복되는 쿼리 있다면 캐시에 넣어두고 빠르게 가져올 수 있는 설정 가능 -->
<setting name="cacheEnabled" value="true" />
<!-- lazyloading 사용 여부 설정 -->
<setting name="laztLoadingEnabled" value="true" />
<!-- lazy loading = 램에 로딩되는 시점 최대한 늦춰서 사용 직전에 로딩 -->
<!-- 하나의 쿼리에서 다중 결과 집합 허용 여부 설정 -->
<setting name="multipleResultSetEnabled" value="true" />
<!-- insert 한 이후 auto increment 한 값 자동으로 가져오는 설정 -->
<setting name="useGeneratedKeys" value="true" />
<!-- 결과값 가지고 오면 (필드명 일치시) set메소드 호출해서 알아서 매핑하는 설정 -->
<setting name="autoMappingBehavior" value="true" />
</settings>
useGeneratedKeys
INSERT INTO board
VALUES (null, #{title}, ...)
// useGeneratedKeys 설정해두면 null 위치에 Auto Increment 값 넣어준다!
/* 결과
boardVO
100, 'fun', 'good' ...;
*/
이번 주 수업에 대해서
광복절이 있어서 수업 일수가 짧았지만 이전 수업에서 JDBC가 어떤 단계로 DB와 데이터를 주고받는지 이해했고, Spring 동작 과정에 대해서도 이해한 뒤 MyBatis를 배워서 그런지 크게 어려운 점은 없었다!
다만 MyBatis 내부 객체 구조는 조금 어렵다고 느꼈지만 그림으로 그려보니 이해할 수 있었다.
