DEV&OPS/Java

Spring MVC PostgreSQL CRUD 예제

ALEPH.GEM 2026. 7. 16. 21:28
728x90

 

 

MyBatis 와 Spring Data JPA 비교

 
특징 MyBatis Spring Data JPA
패러다임 SQL 매퍼 (SQL-Centric) ORM (Object-Relational Mapping) (Object-Centric)
SQL 관리 개발자가 XML이나 어노테이션에 직접 SQL 작성 기본적인 CRUD SQL은 프레임워크가 자동 생성
개발 속도 Mapper와 XML을 모두 작성해야 함 인터페이스 상속만으로 완성 (빠름)
복잡한 쿼리 SQL을 직접 제어하므로 복잡한 조인, 동적 쿼리 작성, 속도 튜닝 등이 용이 JPQL이나 QueryDSL을 사용해야 하며, 때로 복잡함
영속성 관리 실행 후 결과 매핑만 담당 엔티티의 생명주기(조회, 변경 감지, 삭제)를 관리 (영속성 컨텍스트)
 

 

 

MyBatis 및 PostgreSQL 연동을 위한 설정

1. 의존성 추가 (Maven pom.xml 또는 Gradle)

프로젝트에 다음 라이브러리 의존성을 추가해야 합니다.

Spring Boot를 사용하지 않는 경우 Spring JDBC, MyBatis Spring 연동 라이브러리, PostgreSQL JDBC 드라이버 등이 필요합니다.

  • PostgreSQL Driver: 데이터베이스 연결
  • MyBatis & MyBatis-Spring: SQL 매핑 프레임워크 및 Spring 연동
  • Spring JDBC: Spring에서 JDBC 관련 기능을 쉽게 사용하도록 지원

2. PostgreSQL 테이블 생성

Board 모델에 대응하는 데이터베이스 테이블을 생성합니다.

-- PostgreSQL에서 실행할 SQL
CREATE TABLE board (
    id SERIAL PRIMARY KEY,    -- SERIAL은 자동 증가 (AUTO_INCREMENT) 역할
    title VARCHAR(255) NOT NULL,
    content TEXT
);

 

3. 데이터베이스 연결 설정 (Spring 설정 파일)

데이터베이스 연결 정보와 MyBatis 설정을 정의합니다.

일반적으로 root-context.xml 또는 Spring Boot의 application.properties에 설정합니다.

<beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <beans:property name="driverClassName" value="org.postgresql.Driver" />
    <beans:property name="url" value="jdbc:postgresql://localhost:5432/your_db_name" />
    <beans:property name="username" value="your_user" />
    <beans:property name="password" value="your_password" />
</beans:bean>

<beans:bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <beans:property name="dataSource" ref="dataSource" />
    <beans:property name="typeAliasesPackage" value="[Your_Package].model" /> <beans:property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml" />
</beans:bean>

<mybatis-spring:scan base-package="[Your_Package].repository" />

 

4. Repository (DAO) 인터페이스 생성

영속성 계층을 MyBatis를 이용합니다.

BoardService가 사용할 데이터베이스 접근 인터페이스를 정의하고, MyBatis @Mapper 어노테이션을 붙여줍니다.

// src/main/java/.../repository/BoardMapper.java
import [Your_Package].model.Board;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;

@Mapper // MyBatis Mapper로 인식
public interface BoardMapper {
    // C: 새 글 저장
    void insertBoard(Board board);

    // R: 전체 목록 조회
    List<Board> selectAllBoards();

    // R: 특정 ID 글 조회
    Board selectBoardById(Long id);

    // U: 글 수정
    void updateBoard(Board board);

    // D: 글 삭제
    void deleteBoard(Long id);
}

 

5. MyBatis SQL 매퍼 파일 작성

BoardMapper.java에서 정의한 메서드와 매핑될 실제 SQL 쿼리를 XML 파일로 작성합니다.

예: src/main/resources/mappers/BoardMapper.xml

<?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="[Your_Package].repository.BoardMapper">
    
    <insert id="insertBoard" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO board (title, content)
        VALUES (#{title}, #{content})
    </insert>
    
    <select id="selectAllBoards" resultType="[Your_Package].model.Board">
        SELECT id, title, content FROM board ORDER BY id DESC
    </select>
    
    <select id="selectBoardById" resultType="[Your_Package].model.Board">
        SELECT id, title, content FROM board WHERE id = #{id}
    </select>
    
    <update id="updateBoard">
        UPDATE board
        SET title = #{title}, content = #{content}
        WHERE id = #{id}
    </update>
    
    <delete id="deleteBoard">
        DELETE FROM board WHERE id = #{id}
    </delete>

</mapper>

 

6. Service 계층 수정 (MyBatis 연동)

BoardService를 수정하여 BoardMapper를 주입받아 데이터 접근을 위임합니다.

// src/main/java/.../service/BoardService.java (수정된 코드)
import [Your_Package].repository.BoardMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class BoardService {
    
    private final BoardMapper boardMapper;

    @Autowired
    public BoardService(BoardMapper boardMapper) {
        this.boardMapper = boardMapper;
    }

    // 1. Create: 새 글 저장
    public Board save(Board board) {
        // insertBoard 실행 후, MyBatis 설정에 의해 board 객체의 id 필드에 DB에서 생성된 ID가 채워짐
        boardMapper.insertBoard(board); 
        return board;
    }

    // 2. Read All: 모든 글 목록 조회
    public List<Board> findAll() {
        return boardMapper.selectAllBoards();
    }

    // 3. Read One: 특정 ID의 글 조회
    public Board findById(Long id) {
        return boardMapper.selectBoardById(id);
    }

    // 4. Update: 글 수정
    public Board update(Long id, Board updatedBoard) {
        updatedBoard.setId(id); // Mapper에서 WHERE 절에 사용할 수 있도록 ID 설정
        boardMapper.updateBoard(updatedBoard);
        return updatedBoard;
    }

    // 5. Delete: 글 삭제
    public void delete(Long id) {
        boardMapper.deleteBoard(id);
    }
}

 

7. Controller 계층

BoardController는 BoardService의 메서드를 호출하는 역할만 합니다.

영속성 계층의 변경이 서비스 계층까지만 영향을 미치고 컨트롤러 계층에는 영향을 주지 않는 것이 좋은 설계의 예입니다

(SRP 원칙).

// src/main/java/.../controller/BoardController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/boards")
public class BoardController {

    private final BoardService boardService;

    @Autowired
    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

    // **1. Read All (목록 조회)**: GET /boards
    @GetMapping
    public String listBoards(Model model) {
        model.addAttribute("boards", boardService.findAll());
        return "boardList"; // boardList.jsp 또는 boardList.html로 이동
    }

    // **2. Create (등록 폼)**: GET /boards/new
    @GetMapping("/new")
    public String showCreateForm(Model model) {
        model.addAttribute("board", new Board()); // 빈 Board 객체를 모델에 담아 폼에 전달
        return "boardForm";
    }

    // **3. Create (등록 처리)**: POST /boards
    @PostMapping
    public String createBoard(@ModelAttribute Board board) {
        boardService.save(board);
        return "redirect:/boards"; // 목록 페이지로 리다이렉트
    }

    // **4. Read One (상세 조회)**: GET /boards/{id}
    @GetMapping("/{id}")
    public String viewBoard(@PathVariable Long id, Model model) {
        Board board = boardService.findById(id);
        if (board == null) {
            return "redirect:/boards"; // 글이 없으면 목록으로
        }
        model.addAttribute("board", board);
        return "boardDetail"; // boardDetail.jsp로 이동
    }

    // **5. Update (수정 폼)**: GET /boards/{id}/edit
    @GetMapping("/{id}/edit")
    public String showEditForm(@PathVariable Long id, Model model) {
        Board board = boardService.findById(id);
        if (board == null) {
            return "redirect:/boards";
        }
        model.addAttribute("board", board);
        return "boardForm"; // 등록 폼과 동일한 뷰 사용
    }

    // **6. Update (수정 처리)**: POST /boards/{id} (PUT/PATCH는 폼에서 지원하기 어려워 POST 사용)
    @PostMapping("/{id}")
    public String updateBoard(@PathVariable Long id, @ModelAttribute Board board) {
        boardService.update(id, board);
        return "redirect:/boards/" + id; // 상세 페이지로 리다이렉트
    }

    // **7. Delete (삭제 처리)**: POST /boards/{id}/delete
    @PostMapping("/{id}/delete")
    public String deleteBoard(@PathVariable Long id) {
        boardService.delete(id);
        return "redirect:/boards"; // 목록 페이지로 리다이렉트
    }
}

 

 

JPA 및 Postgresql 연동 설정

1. 의존성 추가 (Maven pom.xml 또는 Gradle)

mybatis 대신 spring-data-jpa 의존성이 필요합니다. (Spring Boot 기준)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

 

2. 데이터베이스 연결 설정 (application.properties)

Spring Boot를 사용하면 application.properties 파일에 다음과 같이 간단하게 설정할 수 있습니다.

# src/main/resources/application.properties

# PostgreSQL DataSource 설정
spring.datasource.url=jdbc:postgresql://localhost:5432/your_db_name
spring.datasource.username=your_user
spring.datasource.password=your_password
spring.datasource.driver-class-name=org.postgresql.Driver

# JPA (Hibernate) 설정
spring.jpa.hibernate.ddl-auto=update
# ddl-auto=update : (개발용) Entity 클래스 변경 시 DB 테이블을 자동으로 업데이트합니다. 
# ddl-auto=create : (개발/테스트용) 앱 실행 시마다 테이블을 새로 생성합니다.
# ddl-auto=none : (운영용) 자동 DDL을 비활성화합니다.

spring.jpa.show-sql=true 
# 실행되는 SQL을 로그로 보여줍니다. (개발 시 유용)

 

3. Model (Entity) 수정

영속성 계층은 Board.java 클래스를 JPA가 관리하는 Entity로 변경해야 합니다. 

// src/main/java/.../model/Board.java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity // 이 클래스가 JPA에 의해 관리되는 엔티티임을 선언
@Table(name = "board") // 매핑할 테이블 이름 지정 (PostgreSQL 테이블과 동일)
public class Board {

    @Id // 이 필드가 기본 키(PK)임을 선언
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    // PK 생성 전략을 IDENTITY로 설정 (PostgreSQL의 SERIAL 타입과 매핑됨)
    private Long id;

    private String title;
    private String content;

    // Getter와 Setter (JPA는 기본 생성자와 Getter/Setter가 필요합니다)
    // ... (기존과 동일)
}

 

4. Repository (JPA Repository) 생성

MyBatis의 BoardMapper 인터페이스와 BoardMapper.xml 파일을 대체하는 단 하나의 인터페이스입니다.

JpaRepository를 상속받는 것만으로 별도의 SQL 작성 없이 기본적인 CRUD 메서드가 자동으로 구현됩니다.

// src/main/java/.../repository/BoardRepository.java
import [Your_Package].model.Board;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
// JpaRepository<[관리할 엔티티 클래스], [엔티티의 PK 타입]>
public interface BoardRepository extends JpaRepository<Board, Long> {
    
    // 비어있어도 됩니다!
    // save(), findById(), findAll(), deleteById() 등이 자동으로 제공됩니다.

    // (참고) 만약 특정 필드로 검색하는 메서드가 필요하다면,
    // 메서드 이름 규칙에 맞게 선언만 해주면 됩니다.
    // 예: List<Board> findByTitle(String title);
}

 

5. Service 계층 수정 (JPA 연동)

BoardService가 BoardMapper 대신 BoardRepository를 주입받도록 수정합니다.

// src/main/java/.../service/BoardService.java (수정된 코드)
import [Your_Package].repository.BoardRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional; // findById는 Optional을 반환합니다.

@Service
public class BoardService {

    private final BoardRepository boardRepository;

    @Autowired
    public BoardService(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }

    // 1. Create: 새 글 저장
    // save() 메서드는 ID가 없으면 INSERT, ID가 있으면 UPDATE를 자동으로 수행합니다.
    public Board save(Board board) {
        return boardRepository.save(board);
    }

    // 2. Read All: 모든 글 목록 조회
    public List<Board> findAll() {
        return boardRepository.findAll();
    }

    // 3. Read One: 특정 ID의 글 조회
    public Board findById(Long id) {
        // findById는 Optional<T>를 반환하므로,
        // .orElse(null) 등을 사용해 엔티티를 추출합니다.
        return boardRepository.findById(id).orElse(null);
    }

    // 4. Update: 글 수정
    public Board update(Long id, Board updatedBoard) {
        // JPA의 update는 'find-and-save' 패턴을 사용합니다.
        Optional<Board> optionalBoard = boardRepository.findById(id);
        
        if (optionalBoard.isPresent()) {
            Board existingBoard = optionalBoard.get();
            existingBoard.setTitle(updatedBoard.getTitle());
            existingBoard.setContent(updatedBoard.getContent());
            return boardRepository.save(existingBoard); // 변경된 엔티티를 저장
        } else {
            return null; // 수정할 대상이 없음
        }
    }

    // 5. Delete: 글 삭제
    public void delete(Long id) {
        boardRepository.deleteById(id);
    }
}

 

6. Controller 계층

BoardController는 BoardService의 인터페이스(메서드 시그니처)에 의존하고, 서비스의 메서드 시그니처가 변경되지 않았으므로 컨트롤러 코드는 전혀 수정할 필요가 없습니다.

 

 

 

 

 

 

 

 

 

728x90