섹션 6. 웹 MVC 개발
섹션 7. 스프링 DB 접근 기술
홈 컨트롤러 추가
package hello.hellospring.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
}
회원 관리용 홈
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<div>
<h1>Hello Spring</h1>
<p>회원 기능</p>
<p>
<a href="/members/new">회원 가입</a>
<a href="/members">회원 목록</a>
</p>
</div>
</div> <!-- /container -->
</body>
</html>
컨트롤러가 정적 파일보다 우선순위가 높다.
회원 등록 폼 개발
회원 등록 폼 컨트롤러
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@GetMapping(value = "/members/new")
public String createForm() {
return "members/createMemberForm";
}
}
회원 등록 폼 HTML
(resources/templates/members/createMemberForm)
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<form action="/members/new" method="post">
<div class="form-group">
<label for="name">이름</label>
<input type="text" id="name" name="name" placeholder="이름을 입력하세요">
</div>
<button type="submit">등록</button>
</form>
</div> <!-- /container -->
</body>
</html>
회원 등록 컨트롤러
웹 등록 화면에서 데이터를 전달 받을 폼 객체
package hello.hellospring.controller;
public class MemberForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
회원 컨트롤러에서 회원을 실제 등록하는 기능
@PostMapping(value = "/members/new")
public String create(MemberForm form) {
Member member = new Member();
member.setName(form.getName());
memberService.join(member);
return "redirect:/";
}
회원 컨트롤러에서 조회 기능
@GetMapping(value = "/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
회원 리스트 HTML
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<div>
<table>
<thead>
<tr>
<th>#</th>
<th>이름</th>
</tr>
</thead>
<tbody>
<tr th:each="member : ${members}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
</tr>
</tbody>
</table>
</div>
</div> <!-- /container -->
</body>
</html>
이 중 현재 실무에서 핵심적으로 사용되는 기술은 JPA와 스프링 데이터 JPA이다.
H2 데이터베이스 설치: H2는 개발 및 테스트 용도로 사용되는 경량 데이터베이스이다.
1. 순수 JDBC - 자바에서 관계형 DB에 접근하기 위한 표준 API
DataSource(또는 DriverManager)로 Connection 획득
-> SQL 문자열 작성-> PreparedStatement 생성 + setString, setLong 등 파라미터 세팅
-> executeUpdate() / executeQuery() 실행
-> ResultSet 읽고 Member 같은 도메인 객체로 매핑-> 예외 처리 + 리소스 정리
개발자가 직접 다 처리함...
장점
단점
2. 스프링 JdbcTemplate - 스프링이 제공하는 JDBC 추상화 도구
JDBC를 쓰지만, 그 과정의 반복(커넥션/예외/리소스 정리)을 스프링이 대신 해준다.
장점
단점
3. JPA
JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환힐 수 있다.
build.gradle 파일에 JPA, h2 데이터베이스 관련 라이브러리 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
}
spring-boot-starter-data-jpa는 내부에 jdbc 관련 라이브러리를 포함한다.
스프링 부트에 JPA 설정 추가
resources/application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.datasource.*
-> DB 연결 정보
spring.jpa.show-sql=true
-> JPA가 생성한 SQL을 콘솔에 출력
spring.jpa.hibernate.ddl-auto
-> JPA가 테이블을 자동으로 생성할지 여부를 설정
JPA 엔티티 매핑
package hello.hellospring.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity
-> 이 클래스는 DB 테이블과 매핑됨
@Id
-> PK 지정
@GeneratedValue
-> ID 자동 생성
JPA 회원 리포지토리
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaMemberRepository implements MemberRepository {
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
public Member save(Member member) {
em.persist(member);
}
return member;
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public Optional<Member> findByName(String name) {
List<Member> result = em.createQuery("select m from Member m where
m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
return result.stream().findAny();
}
}
persist → 저장
find → PK 조회
createQuery → JPQL 실행
서비스 계층에 트랜잭션 추가
import org.springframework.transaction.annotation.Transactional
@Transactional
public class MemberService {}
트랜잭션(Transaction)
여러 개의 작업을 하나의 논리적인 단위로 묶어서
모두 성공하거나 모두 실패하도록 보장하는 것
JPA를 사용하도록 스프링 설정 변경
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em);
}
4. 스프링 데이터 JPA