scone-lemon
2022 01 15 토요일 공부 (김영한 스프링 입문 review : 회원 관리 예제 - 백엔드 개발, 스프링 빈과 의존 관계, 회원 관리 예제 - 웹 MVC 개발, 스프링 DB 접근 기술, 테스트 코드 작성은 제외) 본문
2022 01 15 토요일 공부 (김영한 스프링 입문 review : 회원 관리 예제 - 백엔드 개발, 스프링 빈과 의존 관계, 회원 관리 예제 - 웹 MVC 개발, 스프링 DB 접근 기술, 테스트 코드 작성은 제외)
lemon-scone 2022. 1. 15. 14:02
1. 리뷰할 목차
2. 회원 관리 예제 - 백엔드 개발
### 프로젝트 생성
https://start.spring.io/
Dependencies : Spring Web (WEB), Thymeleaf (TEMPLATE ENGINES)
### IntelliJ 단축키
preference : ctrl + alt + s
자동완성 : ctrl + space
빨간줄 제안 : alt + enter
맨 뒤에 ; 찍고 라인 완성 : ctrl + shift + enter
해당 라인에 해당하는 변수 자동 선언 : ctrl + alt + v
https://blog.jetbrains.com/ko/2020/03/11/top-15-intellij-idea-shortcuts_ko/
### 디렉토리 전반적인 구성 요소
컨트롤러 - MVC의 컨트롤러
서비스 - 핵심 비즈니스 로직 구현
리포지토리 - 디비 접근, 도메인 객체를 디비에 저장 및 관리
도메인 - 비즈니스 도메인 객체
### 회원 도메인과 리포지토리 만들기
Member.java (멤버 객체) 생성
- Long id
- String name
MemberRepository.java (리포지토리 인터페이스) 생성
- Member save(Member member)
- Optional<Member> findById(Long id)
- Optional<Member> findByName(String name)
- List<Member> findAll()
Memory1MemberRepository.java (리포지토리 인테페이스 구현체) 생성
- 인터페이스 구현
- 동시성 문제 설명
- Opntional.ofNullable 설명
- store.values().stream().filter().findAny() 설명
### 회원 서비스 개발
MemberService.java (비즈니스 로직) 생성
- Long join(Member member)
- void validateDuplicateMember(Member member)
- List<Member> findMembers()
- Opntional<Member> findOne(Long memberId)
- 중복 회원 검증 함수 및 로직 설명
- ifPresnet() 설명
MemberService 와 MemberRepository 네이밍 비교
- MemoryRepository 에 비해서 네이밍이 비즈니스에 가깝게 사용 (비즈니스 의존적으로 설계)
- MemoryRepository 는 단순히 디비에 데이터를 넣었다 뺐다 하는 기능에 맞도록 네이밍
3. 스프링 빈과 의존관계
### 의존관계
회원 컨트롤러가 회원 서비스와 회원 리포지토리를 사용할 수 있도록 의존관계를 설정함
회원 컨트롤러 --> 회원 서비스 --> 회원 리포지토리
MemberController.java 생성
### Annotation
MemberController, MemberService, MemoryMemberRepository.java 수정, 어노테이션 추가
- @Controller: spring 이 Controller 객체를 생성해서 Spring Container 통에 넣어둠 = bean 이 관리된다 라고 표현
- @Autowired: Spring 이 Container 에 있는 MemberService 를 가져다가 연결시켜 줌 (wired) = 의존관계 주입
- @Service
- @Repository
### 의존관계 주입 (Dependency Injection, DI)
MemberController.java
- @Controller : Spring 이 MemberController 를 Spring Container 에 넣음
- @Autowired : MemberService 를 사용할 수 있도록 생성자에 달아줌, Spring 이 Spring Contrainer 에 있는 MemberService 를 붙여줌 (= 의존성을 주입해줌)
MemberSerivce.java
- @Service : Spring 이 MemberService 를 Spring Container 에 넣음
- @Autowird : MemberRepository 를 사용할 수 있도록 생성자에 달아줌, Spring 이 Spring Contrainer 에 있는 MemberRepository 를 붙여줌 (= 의존성을 주입해줌)
MemoryMemberRepository.java
- @Repository : Spring 이 MemberRepsoitory 를 Spring Container 에 넣음
- MemberController --> MemberService --> MemberRepository (의존관계)
### 스프링 빈을 등록하는 두가지 방법
컴포넌트 스캔과 자동 의존관계 설정 : 어노테이션 사용
자바 코드로 직접 스프링 빈 등록 : SpringConfig.java
### 컴포넌트 스캔과 자동 의존관계 설정
Component, Autowired
- @Controller, @Service, @Repository 가 @Component 를 포함
- Spring 이 Component 와 관련된 어노테이션이 있으면 각각 객체를 생성해서 Container 에 넣어둠
- Spring 이 Autowired 어노테이션이 으로 각각의 객체를 연결해줌 (@Autowired 는 @Component 미포함)
- @Autowired 는 Spring Bean 으로 등록된 객체(스프링이 관리하는 객체)에서만 동작
Scan
- ~Applicaton.java 를 포함한 패키지 및 하위 패키지만 컴포넌트 스캔 대상
- @SpirngBootApplication 이 @ComponentScan 을 포함하기 때문
Spring Bean
- 스프링을 사용 할 때에 왠만한 것들을 Spring Bean 으로 등록해서 사용해야 이점이 많다
- 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때 기본으로 싱글톤으로 등록 (유일하게 하나만 등록해서 공유)
- 따라서 같은 스프링 빈이면 모두 같은 인스턴스
### 자바 코드로 직접 스프링 빈 등록
SpringConfig.java 생성
- @Configuration
- @Bean
의존 관계
- Controller.java 에 있는 @Controller 그냥 두어서 컴포넌트 스캔 하도록
- Service 와 Repository 를 @Bean 을 보고 Contrainer 에 넣음
- return 하는 내용을 보고 Autowired 와 같은 기능을 수행해준다
컴포넌트 스캔과 비교한 장점
- 정형화된 코드 사용시 컴포넌트 스캔 사용
- 정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 스프링 빈 사용 (= 어떤 데이터베이스를 사용할지 정해지지 않은 상황)
- 스프링 빈 등록이나 코드 수정이 간편하다
### DI 세가지 방법
field, setter, constructor 주입
- field 주입 방식 : 바꿀수가 없다(?)
- setter 주입 방식 : public 을 선언되어 아무나 호출할 수 있어서 위험
- constructor : 생성 시점에 한번만 호출, 이 방법을 가장 많이 사용
4. 회원 관리 예제 - 웹 MVC 개발
### 회원 웹 기능 - 홈 화면 추가
HomeController.java 생성
- String home()
home.html 생성
- <a href="/members/new">회원 가입</a>
- <a href="/memebers">회원 목록</a>
### index.html 과 home.html 중 우선순위
- localhost:8080 요청이 왔을 때 Spring Controller 에 관련 Controller 가 있는지 먼저 찾고 없으면 static/ 을 찾는다
- @GetMapping("/") 이 있기 때문에 기존에 만든 index.html 은 무시된다
### 회원 웹 기능 - 등록
MemberController.java 수정
- String createForm()
createMemberForm.html 생성
createMemberForm.html 이 받아온 데이터를 랜더링 하는 부분은 이해가 가는데
createMemberForm.html 이랑 MemberController.java 가 데이터를 주고받는 방식이 이해하기가 어렵다...
### Post 과 Get 의 데이터 전달 방식 차이
- Post : 주로 데이터 등록할 때 사용, 데이터를 폼 같은데 넣어서 전달
- Get : 주로 데이터 조회할 때 사용, url 창에 엔터치는 방식
- url 은 같아도 Get 이냐 Post 냐에 따라서 다르게 매핑할 수 있다
### 회원 웹 기능 - 조회
MemberController.java 수정
- String list(Model model)
memberList.html 생성
memberList.html 가 받아온 데이터를 랜더링 하는 부분은 이해가 가는데
memberList.html 이랑 MemberController.java 가 데이터를 주고받는 방식이 이해하기가 어렵다...
5. 스프링 DB 접근 기술
### H2 데이터베이스
C:\Program Files (x86)\H2\bin>h2.bat
JDBC URL : jdbc:h2:tcp://localhost/~/test (socket으로 연결)
### 데이터베이스에 접근하기 위한 준비
build.gradle 수정 (드라이버 설치)
application.properties 수정 (접속정보 입력)
### 순수 JDBC
Memory2MemberRepository.java 생성
Memory2MemberRepository.java 데이터베이스 연결부터 쿼리 생성까지 과정을 세부적으로 이해하기가 어렵다...
SpringConfig.java 수정
OCP (Open-Close Principle)
- 개방 폐쇄 원칙
- 확장에는 열려있고 변경에는 닫혀있다
- 객체지향의 다형성 개념을 잘 이용하면 기능을 완전히 변경해도 어플리케이션 전체를 수정할 필요가 없다
- 조립하는 코드들은 수정해야 하지만, 실제 어플리케이션이 동작하는데 필요한 코드는 수정할 필요가 없다
DI (Dependency Injection)
- 스프링 DI 를 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.
- @Configuration 에 @Bean 등록 = Spring 이 의존성 주입
### 스프링 JdbcTemplate
JdbcTemplate
- MyBatis 와 같은 라이브러리
- JDBC API 에서 본 반복적인 코드를 대부분 제거
- 하지만 SQL 은 직접 작성
Memory3MemberRepository.java 생성
Memory3MemberRepository.java 데이터베이스 연결부터 쿼리 생성까지 과정을 세부적으로 이해하기가 어렵다...
SpringConfig.java 수정
### JPA
JPA
- 기존의 반복코드는 물론이고 기본적인 SQL 도 JPA 가 직접 만들어서 실행
- JPA 를 사용하면 SQL 과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임은 전환
- JPA 를 사용하면 개발 생산성을 크게 증대
build.gradle dependencies 수정
application.properties 수정
Member.java 수정
- @Entity 설명
- @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 설명
Memory4MemberRepository.java 생성
- EntityManager 설명
- JPQL 설명 : 객체를 대상으로 쿼리를 날림
SpringConfig.java 수정
MemberService.java 수정
NullPointerException 에러 해결 못하고 실습 실패 (아래 에러 내용) ... 인줄 알았는데!
SpringConfig.java 교재 소스처럼 바꿔주었더니 에러 사라짐! 실습 성공!
6. 최종 산출된 프로젝트 파일 및 구조
7. 최종 산출된 소스코드 전체
package com.ssafy.spring1.domain;
package com.ssafy.spring1.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Member {
// id 를 PK 로 지정
// IDENTITY : DB 가 자동으로 값을 생성
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
package com.ssafy.spring1.controller;
package com.ssafy.spring1.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home(){
return "home";
}
}
package com.ssafy.spring1.controller;
import com.ssafy.spring1.domain.Member;
import com.ssafy.spring1.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Controller
// Controller Annotation 의 기능 :
// Spring 이 Controller 객체를 생성해서 Spring Container 통에 넣어둠 == bean 이 관리된다 라고 표현
public class MemberController {
private final MemberService memberService;
@Autowired
// Autowired Annotation 의 기능 :
// Spring 이 Container 에 있는 MemberService 를 가져다가 연결시켜 줌 (wired)
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@GetMapping("/members/new")
public String createForm(){
// members/createMemberForm 페이지로 이동하는 기능만 함
return "members/createMemberForm";
}
@PostMapping("/members/new")
public String create(MemberForm form){
Member member = new Member(); // member 객체 생성
member.setName(form.getName()); // form 에서 얻어온 name 으로 member.name 셋팅
memberService.join(member); // member 객체 회원 가입
return "redirect:/";
}
@GetMapping("/members")
public String list(Model model){
List<Member> members = memberService.findMembers();
// members = key, members 안에 list 로 모든 회원을 조회해서 담아둠
model.addAttribute("members",members);
return "members/memberList";
}
}
package com.ssafy.spring1.controller;
public class MemberForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.ssafy.spring1.repository;
package com.ssafy.spring1.repository;
import com.ssafy.spring1.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
package com.ssafy.spring1.repository;
import com.ssafy.spring1.domain.Member;
import org.springframework.stereotype.Repository;
import java.util.*;
// MemberRepository 인터페이스 구현체
//@Repository
// SpringConfig 에서 직접 스프링 빈 등록
public class Memory1MemberRepository implements MemberRepository{
// 동시성 문제를 고려하지 않은 단순한 형태
private static Map<Long, Member> store = new HashMap<>();
private static Long sequence = 0L;
@Override
public Member save(Member member) {
// sequence 번호를 1 증가
member.setId(++sequence);
// store (map) 에 member id (map key), member 객체 (map value) 저장 (put)
store.put(member.getId(), member);
// member 반환
return member;
}
@Override
public Optional<Member> findById(Long id) {
// Optional : null 이 반환되는 경우를 고려
// Optional.ofNullable : 클라이언트가 뭘 할 수 있게(? 쌤 표현)
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
// stream : store 에 저장된 values 들을 루프를 돔
return store.values().stream()
// filter : member.getName() 값이 파라미터로 넘어온 name 과 같은 경우에만 필터링
.filter(member -> member.getName().equals(name))
// findAny : 하나라도 찾으면 반환, 결과가 Optional 로 반환
.findAny();
}
@Override
public List<Member> findAll() {
// store 에 저장된 values 들을 ArrayList 에 저장해서 반환
return new ArrayList<>(store.values());
}
}
package com.ssafy.spring1.repository;
import com.ssafy.spring1.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;
// MemberRepository 인터페이스 구현체
// JDBC 사용
public class Memory2MemberRepository implements MemberRepository {
private final DataSource dataSource; // 데이터베이스 접속 정보
public Memory2MemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Member save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null; // 연결
PreparedStatement pstmt = null; // sql 구문을 실행
ResultSet rs = null; // 결과를 만들어줌
try {
conn = getConnection(); // 커넥션 가지고 옴
pstmt = conn.prepareStatement(sql, // sql 에 위에 선언한 String sql 넣음
Statement.RETURN_GENERATED_KEYS); // 아이디 값
pstmt.setString(1, member.getName()); // 첫번째 ? 와 매칭
pstmt.executeUpdate(); // 디비에 실제 쿼리가 날라감
rs = pstmt.getGeneratedKeys(); // Statement.RETURN_GENERATED_KEYS 와 매칭해서 반환
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); // ? 에 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);
}
}
// 이렇게 Utils 로부터 connection 을 얻어야 데이터 트랜젝션을 유지시켜 준다
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();
}
}
// Utils 로부터 release 해줘야 한다
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
package com.ssafy.spring1.repository;
import com.ssafy.spring1.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;
// MemberRepository 인터페이스 구현체
// JDBC TEMPLATE 사용
public class Memory3MemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
public Memory3MemberRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id"); // table 명, pk
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 new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
}
};
/*return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};*/
}
}
package com.ssafy.spring1.repository;
import com.ssafy.spring1.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
// MemberRepository 인터페이스 구현체
// JPA 사용
public class Memory4MemberRepository implements MemberRepository {
// EntityManager :
// JPA 는 em 으로 모든 것을 동작
// Spring boot 가 자동으로 EntityManager 만들어 줌
private final EntityManager em;
public Memory4MemberRepository(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() {
// JPQL : 객체한테 쿼리를 날림
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();
}
}
package com.ssafy.spring1.service;
package com.ssafy.spring1.service;
import com.ssafy.spring1.domain.Member;
import com.ssafy.spring1.repository.MemberRepository;
import com.ssafy.spring1.repository.Memory1MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
//@Service
// SpringConfig 에서 직접 스프링 빈 등록
@Transactional
public class MemberService {
// MemoryRepository 에 비해서 네이밍이 비즈니스에 가깝게 사용 (비즈니스 의존적으로 설계)
// MemoryRepository 는 단순히 디비에 데이터를 넣었다 뺐다 하는 기능에 맞도록 네이밍
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/** 회원 가입 **/
public Long join(Member member){
// 중복 회원 검증
validateDuplicateMember(member);
// 통과 하면 저장
memberRepository.save(member);
return member.getId();
}
// 중복 회원 검증 함수
private void validateDuplicateMember(Member member) {
Optional<Member> result = memberRepository.findByName(member.getName());
// ifPresent : result 에 null 이 아니라 값이 있으면 (Optional 때문에 가능)
// null 일 가능성이 있으면 이와 같이 Optional 로 감싸주는 것이 좋다
result.ifPresent(m -> {
throw new IllegalStateException(" 이미 존재하는 회원입니다.");
});
// 두줄로 나눠진 위 코드를 아래 주석처럼 간단하게 쓸 수 있다
/*memberRepository.findByName(member.getName())
.ifPresent(member1 -> {
throw new IllegalStateException("이미 존재하는 회원입니다.")
});*/
}
/** 전체 회원 조회 **/
public List<Member> findMembers(){
return memberRepository.findAll();
}
/** 회원 조회 **/
public Optional<Member> findOne(Long memberId){
return memberRepository.findById(memberId);
}
}
package com.ssafy.spring1.service;
import com.ssafy.spring1.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
// Memory 2,3 (JDBC, JDBC TEMPLATE 에서 사용)
// dataSource : 데이터베이스와 연결할 수 있는 정보를 담고 있음 (Spring 이 알아서 만들어놓음)
private final DataSource dataSource;
// Memory 4 (JPA 에서 사용)
private EntityManager em;
@Autowired
public SpringConfig(DataSource dataSource, EntityManager em) {
this.dataSource = dataSource;
this.em = em;
}
@Bean
public MemberService memberService(){
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository(){
//return new Memory1MemberRepository();
//return new Memory2MemberRepository(dataSource); // JDBC
//return new Memory3MemberRepository(dataSource); // JDBC Template
return new Memory4MemberRepository(em); // JPA
// SpringDataJPA
}
}
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>
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<!-- form 을 작성하고 버튼을 누르면
action 의 url 로 post 방식으로 데이터가 넘어옴
MemberController.java 의 @PostMapping("/members/new") 요기로 감 -->
<form action="/members/new" method="post">
<div class="form-group">
<label for="name">이름</label>
<input type="text"
id="name"
name="name"
placeholder="이름을 입력하세요">
<!-- name : server 로 넘어올 때 key 가 된다
"name" 를 보고 MemberForm.java 의 String name 에 넣어줌 -->
</div>
<button type="submit">등록</button>
</form>
</div> <!-- /container -->
</body>
</html>
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
<div>
<table>
<!-- 표 상단 : 속성명 -->
<thead>
<tr>
<th>#</th>
<th>이름</th>
</tr>
</thead>
<!-- 표 하단 및 내용 : 속성 -->
<thead>
<tbody>
<!-- 탬플릿 앤진이 랜더링하는 부분 :
each == 루프를 돈다
${members} == model 안에 있는 값을 꺼냄
${member.id} == getId 에 접근 값을 가져와서 출력
${member.name} == getName 에 접근 값을 가져와서 출력
-->
<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>
'PROJECT > 2학기 공통' 카테고리의 다른 글
2022 01 18 화요일 공부 (스프링 MVC 1편 백엔드 웹 개발 핵심 기술 : 스프링 MVC 웹 페이지 만들기) (0) | 2022.01.18 |
---|---|
2022 01 16 일요일 공부 (스프링 MVC 1편 백엔드 웹 개발 핵심 기술 : 소개, 웹 애플리케이션 이해, 중간 생략, 스프링 MVC 웹 페이지 만들기) (0) | 2022.01.16 |
2022 01 13 목요일 공부 (모든 개발자를 위한 HTTP 웹 기본 지식 day1 : 소개) (0) | 2022.01.13 |
2022 01 13 목요일 회의 (컨설팅 및 기획리뷰, 기획마무리) (0) | 2022.01.13 |
2022 01 12 수요일 공부 (Sub PJT 1 웹 기술 명세서 : 프로젝트 진행에 참고할 내용, 소프트웨어 요구사항 명세서) (0) | 2022.01.12 |