studio.heelab
[스프링 입문] 본문
강의: 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
회원 웹 기능 - 홈 화면 추가 | 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
회원 웹 기능 - 홈 화면 추가
www.inflearn.com
- 회원 관리 예제 - 웹 MVC 개발
1. 홈 화면 추가
홈 컨트롤러 추가
package hello.hello_spring.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home(){
return "home";
}
}
회원 관리용 홈(home.html)
<!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>
localhost:8080 요청 시 스프링 컨테이너 보기 ->컨트롤러 유무 확인 (static 파일)
2. 등록
회원 등록 폼 컨트롤러
package hello.hello_spring.controller;
import hello.hello_spring.domain.Member;
import hello.hello_spring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@GetMapping("/members/new")
public String createForm(){
return "members/createMemberForm";
}
@PostMapping("/members/new")
public String create(MemberForm form){
Member member = new Member();
member.setName(form.getName());
memberService.join(member);
return "redirect:/";
}
}
회원 등록 폼 HTML(createMemberForm.html)
<!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.hello_spring.controller;
// 웹 등록 화면에서 데이터는 전달 받는 폼 객체
public class MemberForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 회원 컨트롤러에서 회원을 실제 등록하는 기능
@PostMapping("/members/new")
public String create(MemberForm form){
Member member = new Member();
member.setName(form.getName());
memberService.join(member);
return "redirect:/";
}
3. 조회
회원 컨트롤러에서 조회 기능
@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
</tr>
</tbody>
</table>
</div>
th:text="${member.name}"></td>
</div> <!-- /container -->
</body>
</html>
- 스프링 DB 접근 기술
1. H2 데이터베이스 설치
H2: 개발이나 테스트 용으로 가볍고 편리한 DB, 웹 화면 제공
1.4.200 버전 쓰기
실행 후 H2 콘솔에 최초 한 번은 jdbc:h2~/test 로 연결 후 jdbc:h2:tcp://localhost/~/test 접속
테이블 생성(콘솔에 입력)
drop table if exists member CASCADE;
create table member
(
id bigint generated by default as identity,
name varchar(255),
primary key (id)
);
2. 순수 JDBC
build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
스프링부트 데이터베이스 연결 설정 추가
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
JDBC 회원 리포지토리 구현 (참고)
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Member save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn
= getConnection();
pstmt
= conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs
= pstmt.getGeneratedKeys();
if (rs.next()) {
member.setId(rs.getLong(1));
}
else {
throw new SQLException("id 조회 실패");
}
return member;
}
}
}
}
catch (Exception e) {
throw new IllegalStateException(e);
finally {
close(conn, pstmt, rs);
@Override
public Optional<Member> findById(Long id) {
String sql = "select * from member where id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn
= getConnection();
pstmt
= conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs
= pstmt.executeQuery();
if(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return Optional.of(member);
}
else {
return Optional.empty();
}
}
}
}
}
catch (Exception e) {
throw new IllegalStateException(e);
finally {
close(conn, pstmt, rs);
@Override
public List<Member> findAll() {
String sql = "select * from member";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn
= getConnection();
pstmt
= conn.prepareStatement(sql);
rs
= pstmt.executeQuery();
List<Member> members = new ArrayList<>();
while(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
members.add(member);
}
}
}
}
}
return members;
catch (Exception e) {
throw new IllegalStateException(e);
finally {
close(conn, pstmt, rs);
@Override
public Optional<Member> findByName(String name) {
String sql = "select * from member where name = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn
= getConnection();
pstmt
= conn.prepareStatement(sql);
pstmt.setString(1, name);
rs
= pstmt.executeQuery();
if(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return Optional.of(member);
}
return Optional.empty();
}
}
}
catch (Exception e) {
throw new IllegalStateException(e);
finally {
close(conn, pstmt, rs);
}
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
}
catch (SQLException e) {
e.printStackTrace();
}
try {
if (pstmt != null) {
pstmt.close();
}
}
catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
close(conn);
}
}
catch (SQLException e) {
e.printStackTrace();
}
}
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
private final DataSource dataSource;
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
}
}
3. 스프링 통합 테스트
회원 서비스 스프링 통합 테스트
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception {
//Given
Member member = new Member();
member.setName("hello");
//When
Long saveId = memberService.join(member);
//Then
Member findMember = memberRepository.findById(saveId).get();
assertEquals(member.getName(), findMember.getName());
}
@Test
public void 중복_회원_예외() throws Exception {
//Given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
//When
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class,
() -> memberService.join(member2));//예외가 발생해야 한다.
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
@SpringBootTest: 스프링 컨테이너와 테스트 함께 실행
@Transactrional: 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후 항상 롤백한다 ->DB에 데이터가 남지 않아서 다음 테스트에 영향을 주지 않는다
4. 스프링 JdbcTemplate
스프링 JdbcTemplate 회원 리포지토리
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcTemplateMemberRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters));
member.setId(key.longValue());
return member;
}
@Override
public Optional<Member> findById(Long id) {
List<Member> result = jdbcTemplate.query("select * from member where id
= ?", memberRowMapper(), id);
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from member where
name = ?", memberRowMapper(), name);
return result.stream().findAny();
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
}
스프링 설정 변경 (JdbcTemplate 사용)
package hello.hellospring;
**
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
private final DataSource dataSource;
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new JdbcTemplateMemberRepository(dataSource);
}
5.JPA
JPA가 기존의 반복 코드와 기본적인 SQL을 직접 만들어서 실행
SQL과 데이터 중심의 설계에서 객체 중심 설계로 패러다임 전환
개발생산성 향상
6. 스프링 데이터 JPA
스프링 데이터 JPA 회원 리포지 사용(스프링 설정 변경)
package hello.hellospring;
**
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
}'SpringBoot' 카테고리의 다른 글
| SpringBoot 12~14장 (4) | 2025.07.07 |
|---|---|
| Spring Boot 3~5장 (0) | 2025.05.31 |
| Spring Boot 1~2장 (0) | 2025.05.24 |
| [스프링 입문] (0) | 2025.05.09 |