Spring MVC + MyBatis ์‹ค์Šต

2025. 11. 6. 17:53ใ†Java/Mybatis

๐Ÿงฉ Spring MVC + MyBatis ์‹ค์Šต ์ •๋ฆฌ

์ด ๊ธ€์€ Spring MVC + MyBatis ์‹ค์Šต์„ ํŒŒ์ผ๋ณ„๋กœ ๋œฏ์–ด๋ณด๋ฉฐ, XML ๋ฌธ๋ฒ• ํฌ์ธํŠธ์™€ ํ”„๋กœ์ ํŠธ ์‹คํ–‰ ์ „ ์ดˆ๊ธฐ ์„ธํŒ…, ๊ทธ๋ฆฌ๊ณ  MVC ๊ณ„์ธต์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค๊ฐ€๋Š” ํ๋ฆ„์„ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


โถ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ


โท ์‹คํ–‰ ์ „ ์ดˆ๊ธฐ ์„ธํŒ… (application.properties / mybatis-config / Mapper ์œ„์น˜)

๐Ÿ”ง application.properties

spring.application.name=SpringBootMVCDBMybatis2

spring.mvc.view.prefix = /WEB-INF/jsp/
spring.mvc.view.suffix = .jsp


server.servlet.session.persistent=false

# DataSource(Connection Pool)
# Spring Boot use Hicari Connection Pool

spring.datasource.url=jdbc:mysql://localhost:3306/madang?useUnicode=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Mybatis
mybatis.config-location=classpath:/config/mybatis-config.xml
logging.level.com.mycom.myapp =debug

 

๐Ÿงฉ mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	
	
	<settings>
	<setting name ="mapUnderscoreToCamelCase" value="true"/>
	</settings>
  <mappers>
    <mapper resource="mapper/Emp-mapper.xml"/>
    <mapper resource="mapper/Emp-mapper-2.xml"/>
  </mappers>
  
</configuration>

mapUnderscoreToCamelCase=true ์„ค์ •์€ first_name → firstName ์ž๋™ ๋งคํ•‘์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. 

mybatis ๊ด€๋ จ ์„ค์ •๋“ค์€ ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์—์„œ ํ™•์ธํ•ด๋ณด๋ฉด์„œ ์ˆ˜์—…์„ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.


โธ DTO (EmpDto.java)

package com.mycom.myapp.dto;

public class EmpDto {
    private int employeeId;
    private String firstName;  // first_name ๋งคํ•‘
    private String lastName;   // last_name ๋งคํ•‘
    private String email;
    private String hireDate;   // ํƒ€์ž…์„ LocalDate/LocalDateTime์œผ๋กœ ๋ฐ”๊ฟ”๋„ ๋จ

    public EmpDto() {}

    public EmpDto(int employeeId, String firstName, String lastName, String email, String hireDate) {
        this.employeeId = employeeId;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.hireDate = hireDate;
    }

    public int getEmployeeId() { return employeeId; }
    public void setEmployeeId(int employeeId) { this.employeeId = employeeId; }

    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }

    public String getLastName() { return lastName; }
    public void setLastName(String lastName) { this.lastName = lastName; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

    public String getHireDate() { return hireDate; }
    public void setHireDate(String hireDate) { this.hireDate = hireDate; }
}

โœ… JSON ํ‚ค๋Š” getter ์ด๋ฆ„ ๊ธฐ์ค€์œผ๋กœ ์ง๋ ฌํ™”๋ฉ๋‹ˆ๋‹ค. getEmployeeId()๊ฐ€ employeeId ํ‚ค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.


โน DAO (EmpDao.java)

package com.mycom.myapp.dao;

import java.util.List;
import java.util.Map;

import com.mycom.myapp.dto.EmpDto;

public interface EmpDao {
    List<EmpDto> listEmp();
    EmpDto detailEmp(int employeeId);
    int insertEmp(EmpDto empDto);
    int updateEmp(EmpDto empDto);
    int deleteEmp(int employeeId);

    // ๊ฒ€์ƒ‰/์‹ค์Šต
    List<EmpDto> listEmpLike(String searchWord);
    List<EmpDto> listEmpMap();
    List<EmpDto> listEmpWhereIf(Map<String,String> map);
}

โบ Mapper XML โ‘  (Emp-mapper.xml) — resultType + alias


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mycom.myapp.dao.EmpDao">

  <select id="listEmp" resultType="com.mycom.myapp.dto.EmpDto">
    SELECT 
      employeeId,
      first_name  AS firstName,
      last_name   AS lastName,
      email,
      hire_date   AS hireDate
    FROM emp
  </select>

  <select id="detailEmp" resultType="com.mycom.myapp.dto.EmpDto">
    SELECT 
      employeeId,
      first_name  AS firstName,
      last_name   AS lastName,
      email,
      hire_date   AS hireDate
    FROM emp
    WHERE employeeId = #{employeeId}
  </select>

  <insert id="insertEmp" parameterType="com.mycom.myapp.dto.EmpDto">
    INSERT INTO emp (employeeId, first_name, last_name, email, hire_date)
    VALUES (#{employeeId}, #{firstName}, #{lastName}, #{email}, #{hireDate})
  </insert>

  <update id="updateEmp" parameterType="com.mycom.myapp.dto.EmpDto">
    UPDATE emp
    SET first_name = #{firstName},
        last_name  = #{lastName},
        email      = #{email},
        hire_date  = #{hireDate}
    WHERE employeeId = #{employeeId}
  </update>

  <delete id="deleteEmp" parameterType="int">
    DELETE FROM emp
    WHERE employeeId = #{employeeId}
  </delete>

</mapper>

โœ… ๋ฌธ๋ฒ• ํฌ์ธํŠธ
- resultType : SELECT ๊ฒฐ๊ณผ๋ฅผ DTO๋กœ ๋ณ€ํ™˜(ํ”„๋กœํผํ‹ฐ๋ช…๊ณผ ์ปฌ๋Ÿผ/alias ๋งค์นญ)
- parameterType : ์ „๋‹ฌ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… ์ง€์ • (์ƒ๋žต ๊ฐ€๋Šฅํ•˜๋‚˜ ๋ช…์‹œ๊ฐ€ ๊ฐ€๋…์„ฑ↑)
- ์„ธ๋ฏธ์ฝœ๋ก (;)์€ ๋ณดํ†ต XML ๋‚ด๋ถ€์— ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.


โป Mapper XML โ‘ก (Emp-mapper-2.xml) — <where> / <if> / resultMap

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mycom.myapp.dao.EmpDao">

  <!-- resultMap : ์ปฌ๋Ÿผ๋ช…์ด ์• ๋งคํ•˜๊ฑฐ๋‚˜ ์กฐ์ธ/๋ณตํ•ฉ ๋งคํ•‘์— ์œ ์šฉ -->
  <resultMap id="empMap" type="com.mycom.myapp.dto.EmpDto">
    <id     property="employeeId" column="employeeId"/>
    <result property="firstName"  column="first_name"/>
    <result property="lastName"   column="last_name"/>
    <result property="email"      column="email"/>
    <result property="hireDate"   column="hire_date"/>
  </resultMap>

  <select id="listEmpMap" resultMap="empMap">
    SELECT employeeId, first_name, last_name, email, hire_date
    FROM emp
  </select>

  <!-- where/if : ์กฐ๊ฑดํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ WHERE ์ ˆ์„ ๊ตฌ์„ฑ -->
  <select id="listEmpWhereIf" parameterType="map" resultType="com.mycom.myapp.dto.EmpDto">
    SELECT employeeId,
           first_name AS firstName,
           last_name  AS lastName,
           email,
           hire_date  AS hireDate
    FROM emp
    <where>
      <if test="firstName != null">
        first_name = #{firstName}
      </if>
      <if test="lastName != null">
        AND last_name = #{lastName}
      </if>
      <if test="email != null">
        AND email = #{email}
      </if>
    </where>
  </select>

</mapper>

โœ… ๋ฌธ๋ฒ• ํฌ์ธํŠธ
- <where>๋Š” ๋‚ด๋ถ€ ์กฐ๊ฑด์ด ํ•˜๋‚˜๋ผ๋„ ์ฐธ์ด๋ฉด ์ž๋™์œผ๋กœ WHERE๋ฅผ ๋ถ™์ด๊ณ , ์ฒซ ์กฐ๊ฑด ์•ž์˜ AND๋Š” ์ƒ๋žต๊ฐ€๋Šฅํ•˜๋‹ค.
- resultMap vs resultType : ๊ฐ„๋‹จ ๋งคํ•‘์€ resultType + alias, ๋ณต์žก ๋งคํ•‘/์กฐ์ธ/์ปฌ๋Ÿผ๋ช…์ด ์• ๋งคํ•˜๋ฉด resultMap.


โผ Service ๊ณ„์ธต

EmpService.java

package com.mycom.myapp.service;

import java.util.List;
import java.util.Map;
import com.mycom.myapp.dto.EmpDto;

public interface EmpService {
    List<EmpDto> listEmp();
    EmpDto detailEmp(int employeeId);
    int insertEmp(EmpDto empDto);
    int updateEmp(EmpDto empDto);
    int deleteEmp(int employeeId);

    List<EmpDto> listEmpLike(String searchWord);
    List<EmpDto> listEmpMap();
    List<EmpDto> listEmpWhereIf(Map<String,String> map);
}

EmpServiceImpl.java

@Service
public class EmpServiceImpl implements EmpService {

    private final EmpDao empDao;

    public EmpServiceImpl(EmpDao empDao) {
        this.empDao = empDao;
    }

    @Override public List<EmpDto> listEmp() { return empDao.listEmp(); }
    @Override public EmpDto detailEmp(int employeeId) { return empDao.detailEmp(employeeId); }
    @Override public int insertEmp(EmpDto empDto) { return empDao.insertEmp(empDto); }
    @Override public int updateEmp(EmpDto empDto) { return empDao.updateEmp(empDto); }
    @Override public int deleteEmp(int employeeId) { return empDao.deleteEmp(employeeId); }

    @Override public List<EmpDto> listEmpLike(String searchWord) { return empDao.listEmpLike(searchWord); }
    @Override public List<EmpDto> listEmpMap() { return empDao.listEmpMap(); }
    @Override public List<EmpDto> listEmpWhereIf(Map<String,String> map) { return empDao.listEmpWhereIf(map); }
}

 

โœ… ์„œ๋น„์Šค๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ง€๊ธˆ์€ ๋‹จ์ˆœ ์œ„์ž„์ด์ง€๋งŒ, ์ถ”ํ›„ ํŠธ๋žœ์žญ์…˜/๊ฒ€์ฆ/์กฐํ•ฉ ๋กœ์ง์„ ์—ฌ๊ธฐ์— ๋„ฃ์Šต๋‹ˆ๋‹ค.


โฝ Controller ๊ณ„์ธต

EmpController.java (๋ฐ์ดํ„ฐ JSON ์‘๋‹ต)

@RestController
@RequestMapping("/emps")
public class EmpController {

    private final EmpService empService;

    public EmpController(EmpService empService) {
        this.empService = empService;
    }

    @GetMapping("/list")
    public List<EmpDto> listEmp(){ return empService.listEmp(); }

    @GetMapping("/detail/{employeeId}")
    public EmpDto detailEmp(@PathVariable Integer employeeId){
        return empService.detailEmp(employeeId);
    }

    @GetMapping("/listEmpWhereIf")
    public List<EmpDto> listEmpWhereIf(@RequestParam Map<String,String> map){
        return empService.listEmpWhereIf(map);
    }
}

PageController.java (๋ทฐ ๋ผ์šฐํŒ…)

@Controller
public class PageController {
    @GetMapping("/emps") public String emps(){ return "emps"; }
    @GetMapping("/salaries") public String salaries(){ return "salaries"; }
    @GetMapping("/stores") public String stores(){ return "stores"; }
}

โœ… ๋ฐ์ดํ„ฐ๋Š” @RestController(ํ˜น์€ @Controller + @ResponseBody)์—์„œ JSON์œผ๋กœ ๋ณด๋‚ด๊ณ , JSP ํ™”๋ฉด ์ด๋™์€ ๋ณ„๋„ PageController๊ฐ€ ๋‹ด๋‹นํ•˜๋ฉด ์—ญํ• ์ด ๋ถ„๋ฆฌ๋˜์–ด ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค.


โพ View (emps.jsp) + fetch Ajax

<h1>์‚ฌ์› ๊ด€๋ฆฌ</h1>
<table id="empTable" border="1">
  <thead><tr>
    <th>employeeId</th><th>firstName</th><th>lastName</th><th>email</th><th>hireDate</th>
  </tr></thead>
  <tbody id="empTbody"></tbody>
</table>

<script>
  window.onload = () => listEmp();

  async function listEmp(){
    const res = await fetch('/emps/list');
    const data = await res.json();
    render(data);
  }

  function render(list){
    const rows = list.map(emp => `
      <tr data-employeeid="${emp.employeeId}" style="cursor:pointer">
        <td>${emp.employeeId}</td>
        <td>${emp.firstName}</td>
        <td>${emp.lastName}</td>
        <td>${emp.email}</td>
        <td>${emp.hireDate}</td>
      </tr>`).join('');
    document.querySelector('#empTbody').innerHTML = rows;

    document.querySelectorAll('#empTbody tr').forEach(tr => {
      tr.onclick = () => detailEmp(tr.getAttribute('data-employeeid'));
    });
  }

  async function detailEmp(employeeId){
    const res = await fetch('/emps/detail/' + employeeId);
    console.log(await res.json());
  }
</script>

โœ… JSP ์•ˆ ๋ฐฑํ‹ฑ ํ…œํ”Œ๋ฆฟ์—์„œ ${...} EL ์ถฉ๋Œ์„ ํ”ผํ•˜๋ ค๋ฉด ์Šคํฌ๋ฆฝํ‹€๋ฆฟ/EL์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ˆœ์ˆ˜ JS๋กœ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒŒ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.


โฟ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ดํ•ด (์š”์ฒญ → ์‘๋‹ต)

[๋ธŒ๋ผ์šฐ์ € emps.jsp] --fetch--> [EmpController /emps/list]
   → EmpService.listEmp()
     → EmpDao.listEmp()
       → Emp-mapper.xml (select id="listEmp")
         → DB(emp ํ…Œ์ด๋ธ”) ์กฐํšŒ
       ← List<EmpDto>
     ← List<EmpDto>
   ← JSON (List<EmpDto>)
[๋ธŒ๋ผ์šฐ์ €] ํ…Œ์ด๋ธ” ๋ Œ๋”๋ง

ํ•ต์‹ฌ์€ Controller URL๊ณผ Mapper id, ๊ทธ๋ฆฌ๊ณ  Service/Dao ์ธํ„ฐํŽ˜์ด์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ์ •ํ™•ํžˆ ๋งž๋ฌผ๋ฆฌ๋Š”๊ฐ€์ž…๋‹ˆ๋‹ค.

 

 


๋งˆ๋ฌด๋ฆฌ

์ด๋ฒˆ ์‹ค์Šต์˜ ํ•ต์‹ฌ์€ MVC ๊ณ„์ธต์˜ ์ˆœ์„œ์™€ ์ฑ…์ž„์„ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๊ณ , DTO ์ด๋ฆ„/Getter, Mapper XML์˜ id/alias, Controller URL์ด ์œ ๊ธฐ์ ์œผ๋กœ ๋งž๋ฌผ๋ฆฌ๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๊ธ€์˜ ์ˆœ์„œ(DB → DTO → Mapper(XML) → DAO → Service → Controller → JSP)๋ฅผ ์Šต๊ด€ํ™”ํ•ด๋‘๋ฉด, ์ƒˆ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ๋•Œ๋„ ํ”๋“ค๋ฆผ ์—†์ด ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.