오늘은 StartBoot04 프로젝트에서 Spring Data JPA를 사용해 기존 게시판 흐름을 다시 구현했다.
이전에는 MyBatis Mapper에 SQL을 직접 작성했다면, 오늘은 Entity와 Repository를 중심으로 DB 작업을 처리했다.
1. JPA 프로젝트 설정
JPA를 사용하려면 DB 연결 정보와 Hibernate 설정이 필요하다.
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/XEPDB1
spring.datasource.username=multi
spring.datasource.password=multi
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
ddl-auto=validate는 Entity와 DB 테이블 구조가 맞는지 검사한다.
테이블이나 시퀀스가 맞지 않으면 서버 시작 단계에서 에러가 난다.
2. Entity 클래스
JPA에서는 DB 테이블과 연결되는 Java 객체를 Entity라고 한다.
@Entity
@Table(name="MYBOARD")
public class JpaEntity {
@Id
@SequenceGenerator(name="my_seq", sequenceName="SEQ_MY", allocationSize=1)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="my_seq")
@Column
private int myno;
@Column(updatable=false, nullable=false)
private String myname;
@Column
private String mytitle;
@Column
private String mycontent;
@Column(updatable=false, nullable=false)
private Date mydate;
}
핵심은 @Entity, @Table, @Id다.
@Entity
- 이 클래스가 JPA가 관리하는 DB 객체라는 뜻
@Table
- 실제 DB 테이블 이름 지정
@Id
- 기본키 필드 지정
기존 DB에서는 SEQ_MY 시퀀스를 사용하고 있었기 때문에 @SequenceGenerator로 JPA가 이 시퀀스를 사용하게 설정했다.
3. Repository와 JpaRepository
JPA에서는 DAO 역할을 Repository 인터페이스로 만들 수 있다.
@Repository
public interface JpaDao extends JpaRepository<JpaEntity, Integer> {
public JpaEntity findByMyno(int myno);
//JPQL
@Query("DELETE FROM JpaEntity J WHERE J.myno = :myno")
@Modifying
@Transactional
public void delBoard(@Param("myno") int myno);
}
JpaRepository<JpaEntity, Integer>는 이렇게 해석할 수 있다.
JpaEntity : 이 Repository가 관리할 Entity
Integer : Entity의 기본키 타입
JpaRepository를 상속하면 기본 CRUD 메서드를 바로 사용할 수 있다.
dao.findAll();
dao.save(dto);
dao.deleteById(myno);
4. findBy 메서드 규칙
Spring Data JPA는 메서드 이름을 보고 쿼리를 자동으로 만들어준다.
public JpaEntity findByMyno(int myno);
이 코드는 myno 필드로 데이터를 조회한다는 뜻이다.
findBy필드명
= 해당 필드 조건으로 조회
중요한 점은 DB 컬럼명이 아니라 Entity 필드명을 기준으로 작성한다는 것이다.
5. JPQL에서 주의할 점
JPQL은 SQL과 비슷해 보이지만 기준이 다르다.
@Query("DELETE FROM JpaEntity J WHERE J.myno = :myno")
SQL이라면 테이블명과 컬럼명을 사용한다.
DELETE FROM MYBOARD WHERE MYNO = ?
하지만 JPQL은 Entity명과 필드명을 사용한다.
MYBOARD X -> JpaEntity O
MYNO X -> myno O
그래서 J.MYNO처럼 쓰면 에러가 난다.JpaEntity 클래스 안에는 MYNO가 아니라 myno 필드가 있기 때문이다.
6. Controller CRUD 흐름
목록 조회는 findAll()을 사용했다.
@GetMapping("/list")
public String selectAll(Model model) {
List<JpaEntity> list = dao.findAll(Sort.by(Sort.Direction.DESC, "myno"));
model.addAttribute("list", list);
return "jpalist";
}
상세 조회는 findByMyno()를 사용했다.
@GetMapping("/detail")
public String selectOne(Model model, int myno) {
JpaEntity dto = dao.findByMyno(myno);
model.addAttribute("dto", dto);
return "jpadetail";
}
등록은 save()를 사용했다.
@PostMapping("/insert")
public String insert(JpaEntity dto) {
dto.setMydate(new Date(System.currentTimeMillis()));
dao.save(dto);
return "redirect:/board/list";
}
mydate는 form에서 입력받지 않기 때문에 Controller에서 직접 현재 날짜를 넣어주었다.
7. save()의 insert/update 판단
JPA의 save()는 insert와 update를 모두 처리할 수 있다.
dao.save(dto);
기준은 기본키다.
id가 없거나 DB에 없는 값이면 insert
id가 있고 DB에 같은 데이터가 있으면 update
수정에서는 기존 데이터를 다시 조회한 뒤 제목과 내용만 바꿔 저장했다.
@PostMapping("/update")
public String update(JpaEntity dto) {
JpaEntity Dto = dao.findByMyno(dto.getMyno());
Dto.setMytitle(dto.getMytitle());
Dto.setMycontent(dto.getMycontent());
dao.save(Dto);
return "redirect:/board/detail?myno=" + dto.getMyno();
}
이렇게 하면 form에서 넘어오지 않은 myname, mydate 값이 유지된다.
8. Thymeleaf 화면 처리
목록 화면에서는 th:each로 데이터를 반복 출력했다.
<tr th:each="dto : ${list}">
<td th:text="${dto.myno}">myno</td>
<td th:text="${dto.myname}">myname</td>
<td>
<a th:href="@{/board/detail(myno=${dto.myno})}" th:text="${dto.mytitle}">
mytitle
</a>
</td>
<td th:text="${dto.mydate}">mydate</td>
</tr>
상세, 수정 화면에서는 th:object와 *{} 문법을 사용했다.
<form action="/board/update" method="post" th:object="${dto}">
<input type="hidden" name="myno" th:value="*{myno}">
<input type="text" name="mytitle" th:value="*{mytitle}">
<textarea name="mycontent" th:text="*{mycontent}"></textarea>
</form>
th:object="${dto}"를 지정하면 내부에서 *{mytitle}처럼 간단히 접근할 수 있다.
9. 개인적으로 추가해본 삭제 알림
수업 핵심 내용은 아니지만, 삭제 후 알림을 띄우는 기능도 추가해봤다.
Controller에서 삭제 후 메시지 파라미터를 붙여 redirect했다.
@GetMapping("/delete")
public String delete(int myno) {
dao.delBoard(myno);
return "redirect:/board/list?msg=delete";
}
목록 페이지에서는 msg=delete가 있으면 alert를 띄운다.
<script th:if="${param.msg != null and param.msg[0] == 'delete'}">
alert("삭제 완료!");
</script>
Controller는 브라우저 alert를 직접 띄울 수 없기 때문에, 메시지를 URL로 넘기고 View에서 JavaScript로 처리했다.
정리
오늘은 Spring Data JPA로 게시판 CRUD 흐름을 구현했다.
핵심은 다음과 같다.
Entity
- DB 테이블과 연결되는 Java 객체
JpaRepository
- 기본 CRUD 메서드를 제공하는 Repository
findBy필드명
- 메서드 이름으로 조건 조회 쿼리 생성
JPQL
- 테이블/컬럼명이 아니라 Entity/필드명을 사용
save()
- 기본키 상태에 따라 insert 또는 update 처리
Thymeleaf
- Entity 데이터를 화면에 출력하고 form으로 다시 전달
MyBatis는 SQL을 직접 작성하는 느낌이 강했고, JPA는 객체와 메서드 이름을 중심으로 DB 작업을 처리한다는 점이 가장 큰 차이였다.
'멀티캠퍼스' 카테고리의 다른 글
| [2026.07.09] - TIL 69일차 (Spring Security와 로그인 구현) (0) | 2026.07.09 |
|---|---|
| [2026.07.07] - TIL 67일차 (JSP 게시판을 Thymeleaf로 전환하기) (0) | 2026.07.07 |
| [2026.07.06] - TIL 66일차 (Spring Boot 게시판 보완과 Thymeleaf 시작) (0) | 2026.07.07 |
| [2026.06.25 ~ 2026.06.26] - TIL 59~60일차 MSA 특강 정리 (0) | 2026.06.30 |
| [2026.06.24] - TIL 58일차 개발자를 위한 UI/UX 특강 정리 (0) | 2026.06.25 |