본문 바로가기
Sesac 웹 풀스택[새싹X코딩온]/Spring

5. JDBC, MyBatis 연결

by 이쟝 2023. 3. 16.

JDBC(Java Database Connectivity)

자바 언어와 DB를 연결해주는 통로, 자바에서 데이터베이스에 접근할 수 있도록 해주는 자바 API

SQL Mapper 

Object와 SQL의 필드를 매핑해 데이터를 객체화하는 기술
  • 객체와 테이블 간의 관계를 매핑하는 것과 다르다
  • SQL 문을 직접 작성해야 한다.
  • ex) JDBCTemplate, MyBatis

MyBatis

- SQL Mapper 로써, JDBC로 처리하는 상당 부분의 코드와 파라미터 설정 및 결과 매핑을 대신해준다.
- 객체 지향 언어인 자바와 관계형 데이터베이스 프로그래밍을 좀 더 쉽게 할 수 있도록 도와주는 프레임워크

MyBatis 특징

  • 쉬운 접근성과 코드의 간결함
    • JDBC의 모든 기능을 MyBatis에서 사용 가능
    • 복잡한 JDBC 코드를 걷어내 깔끔한 소스코드 유지 가능
  • SQL 문과 프로그래밍 코드의 분리
    • SQL 문과 프로그래밍 코드가 분리되어 있어 SQL에 변경이 있을 때 다시 컴파일 할 필요가 없다.
  • 다양한 프로그래밍 언어로 구현 가능
    • JAVA, C#, .NET, Ruby
  • SQL 문을 직접 작성하기 때문에 수정사항이 생기면 직접 다 수정해야 한다.

MyBatis 동작 원리 

  • 애플리케이션 시작 시 SqlSessionFactoryBuilder가 설정 파일을 참고해 SqlSessionFactory 생성
    • SqlSessionFactory : 데이터베이스와의 연결과 SQL의 실행에 대한 모든 것을 가진 중요한 객체
  • DB 작업 시 SqlSessionFactory가 SqlSession 생성
    • SqlSession : Connection을 생성하거나 원하는 SQL을 전달하고, 결과를 Return 해주는 객체 
  • 생성된 SqlSession을 참고해 mapper 인터페이스 호출
  • Mapper가 SqlSession을 호출해 SQL 실행 후 결과 Return 

MyBatis 시작하기

1. Build.gradle 수정

  • MyBatis를 사용하기 위해 아래 코드를 dependencies에 추가하기
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
runtimeOnly 'com.mysql:mysql-connector-j'

2. application.properties 수정

  • application.properties
    • 런타임 시 다양한 환경에서 동작할 수 있도록 필요한 옵션들을 제공하는데 사용되는 파일
    • 기본 포트도 여기서 바꿀 수 있음
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=yes&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Seoul
spring.datasource.username=root
spring.datasource.password=1234

mybatis.type-aliases-package=com.spring.boot.mapper
mybatis.mapper-locations=mybatis-mapper/*.xml
  • test라는 database
  • 물음표 뒤에 내용은 부가적인 내용
  • username은 사용자, password는 비밀번호

3. 필요한 각 폴더 만들기

  • Domain은 이 비즈니스에서의 핵심 로직을 담고 있다. (만약 express에서 만든 것을 nest.js로 바꾼다고 했을 때)
  • 만들고 난 뒤에 에러가 아래처럼 나와야 DB가 연결된 것을 알 수 있다.

4. MainController

@Controller
public class MainController {

    @Autowired
    MainService mainService;

    @GetMapping("/users")
    public String getUsers(Model model) {
        ArrayList<UserDTO> userList = (ArrayList<UserDTO>) mainService.getUserList();
        model.addAttribute("list", userList);
        return "user";
    }

    @GetMapping("/user/insert")
    public String getInsertUser(@RequestParam String name, @RequestParam String nickname, Model model) {
        User user = new User();
        user.setName(name);
        user.setNickname(nickname);

        mainService.addUser(user);

        model.addAttribute("list", null);
        return "user";
    }

}

5. domain/User

public class User {

    private int id;
    private String name;
    private String nickname;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getNickname() {
        return nickname;
    }
    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
}

아니면 lombok 패키지를 사용해서 @Getter @Setter를 만들 수 있다.

package sesac.jdbc.sesac.jdbc.domain;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class User {

    private int id;
    private String name;
    private String nickname;

}

6. UserDTO

package sesac.jdbc.sesac.jdbc.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserDTO {
    private int id;
    private String name;
    private String nickname;
    private int no; // 클라이언트에게 전달할 때 값을 추가하기 위해서 생성함

}

7. MainService

@Service
public class MainService {

    @Autowired
    private MainMapper mainMapper;

    public List<UserDTO> getUserList() {
        // userDTO가 담긴 List를 controller에 넘겨준다.
        List<User> result = mainMapper.retrieveAll(); // selectAll()과 동일
        List<UserDTO> users = new ArrayList<UserDTO>();


        for(int i=0; i<result.size();i++) {
            UserDTO user = new UserDTO();
            user.setId(result.get(i).getId());
            user.setName(result.get(i).getName());
            user.setNickname(result.get(i).getNickname());
            user.setNo(i+1); // no는 전달하기 위한 값

            users.add(user);
        }
        return users;
    }

    public void addUser(User user) {
        mainMapper.insertUser(user);
    }
}

8. mapper/MainMapper

@Mapper
public interface MainMapper {

    // MainMapper.xml 참고하기
    List<User> retrieveAll(); // MainMapper.xml을 참고하는 작업

    // MainMapper.xml 참고 안 하기
    // insertUser를 실행할 때 @Insert sql문을 실행한다.
    // @Insert 는 mybatis Annotation으로 sql이 insert문이라는 것을 나타냄
    @Insert("insert into user(name, nickname) values(#{name}, #{nickname})")
    void insertUser(User user);

}

9. mybatis-mapper/MainMapper.xml

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

<mapper namespace="sesac.jdbc.sesac.jdbc.mapper.MainMapper">
    <select id="retrieveAll" resultType="sesac.jdbc.sesac.jdbc.domain.User">
        SELECT user.* FROM user
    </select>
</mapper>

- 실행했을 때 오류가 안생기면 성공!

10.templates/user.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h5>users</h5>
<table>
    <tr>
        <th>No.</th>
        <th>ID</th>
        <th>이름</th>
        <th>닉네임</th>
    </tr>
    <tr th:each="user:${list}">
        <td th:text="${user.getNo()}">번호</td>
        <td th:text="${user.getId()}">ID</td>
        <td th:text="${user.getName()}">이름</td>
        <td th:text="${user.getNickname()}">닉네임</td>
    </tr>
</table>
</body>
</html>

1. MainController에 있는 getUsers( ) 실행

2. MainService에 있는 getUserList( ) 실행

3. MainMapper에 있는 retrieveAll이라는 함수 실행

4. MainMapper 인터페이스에 있는 함수는 MainMapperxml안에 있는 sql문이랑 연결됨

3. namespace와, id로 retrieveAll 함수가 연결된다. resultType에는 테이블

5. MainService에서 데이터를 교환할 때는 DTO를 사용하기 때문에 실제로 컨트롤러에 데이터를 전달할 때는 userDTO에 담아서 전달한다.