📖/spring(김영한)

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 4일차

모팔구 2023. 8. 2. 23:27
728x90
반응형

섹션5 회원 관리 예제 - 웹 MVC 개발

회원 웹 기능 - 홈 화면 추가

  • 스프링 부트가 스프링 컨테이너 내에 루트 관련 컨트롤러가 없으면 static 폴더의 index.html 파일을 보여주는데 만약 관련 컨트롤러가 있으면 static 폴더의 파일은 무시된다 
  • 실습함

회원 웹 기능 - 등록

  • form태그에서 인풋 태그의 name을 지정하면 action의 url을 따라 이동하는데 name에 지정한 값(ex. name=name)과 같은 이름의 필드를 찾고 그 필드에 자동으로 값을 대입한다
  • @GetMapping와 @PostMapping에 같은 url을 지정할 수 있는데 form태그의 메서드가 post일 경우 submit하면서 @PostMapping이 지정된 메서드를 동작시킨다

회원 웹 기능 - 조회

@GetMapping("/members")
public String list(Model model) {
	List<Member> members = memberService.findMembers();
	model.addAttribute("members", members);
    
	return "members/memberList";
}

model.addAttribute("members", members)로 members 데이터(두번째 인자)를 members라는 이름(첫번째 인자)으로 전송한다

<!-- th: 타임리프 별칭 -->
<tr th:each="member : ${members}">
	<td th:text="${member.id}"></td>
	<td th:text="${member.name}"></td>
</tr>

th:each: 타임리프로 반복문을 사용할 때 쓰는 문법으로 forEach문과 비슷하다고 생각하면 된다 ${members}는 컨트롤러에서 addAttribute의 첫번째 인자로, 해당 문자열을 통해서 members(List) 데이터를 받는다 tr에 each문을 작성했으므로 tr 한 묶음이 반복된다

th:text: 데이터를 텍스트 형태로 읽어옴 member.id와 member.name은 List<Member>로 가져온 member의 id와 name 필드의 데이터를 읽어오는 것이다

 

섹션6 스프링 DB 접근 기술

H2 데이터베이스 설치

  • h2데이터베이스 설치
    • 사이트(http://www.h2database.com/html/main.html)로 가서 운영체제에 맞는 파일을 다운로드 받는다. 난 맥이라서 All platforms zip파일을 받았다
    • 받고 압축을 푼 뒤에 터미널을 열어서 bin 폴더로 이동한다.
    • chmod 755 h2.sh 명령어로 권한을 바꾼다
      chmod 755 h2.sh
    • ./h2.sh 명령어로 파일을 실행한다
      ./h2.sh

      그럼 아래 사진과 같은 창이 뜸

      만약 창이 로딩중이고 url이 http://218.38.137.28:8082/~~ 이런ㅅ식으로 생기면 :8082 앞을 localhost로 바꾼다
    • 위 사진처럼 기본적으로 기입된 상태로 연결버튼을 클릭하면 dbms 화면으로 넘어간다 그럼 자신의 홈 디렉토리에 test.mv.db파일이 생성됨을 확인할 수 있다 이 파일이 만들어진 후 파일로 접근하게 되면(jdbc:h2:~/test) 애플리케이션이랑 웹 콘솔이 같이 접근이 안될 수 있기 때문에 JDBC URL 자리를 jdbc:h2:tcp://localhost/~/test로 바꾼다 이렇게 지정하면 파일로 접근하지 않고 소켓으로 접근하기 때문에 여러 군데에서 접근할 수 있다 이후 연결을 클릭한다
    • 이렇게 연결할 url을 저장한 뒤에 h2를 완전히 종료하고 test.mv.db 파일을 지운 뒤 다시 실행하면 나중에 수정한 url(jdbc:h2:tcp://localhost/~/test)로 저장되어 있음을 확인할 수 있다(난 지우니 안돼서 걍 일일이 바꿨음)

순수 JDBC

  • build.gradle
    dependencies {
    	...
    	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    	runtimeOnly 'com.h2database:h2'
        ...
    }
  • resources/application.properties
    spring.datasource.url=jdbc:h2:tcp://localhost/~/test
    spring.datasource.driver-class-name=org.h2.Driver
    spring.datasource.username=sa

스프링 통합 테스트

  • @SpringBootTest: 스프링 컨테이너와 테스트를 함께 실행한다.
  • @Transactional: 테스트 케이스에 이 어노테이션이 있으면 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 데이터베이스에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다. 테스트 케이스가 아닌 클래스(서비스 등)에 있을 경우 롤백하지 않고 정상적으로 돌아간다.
  • 단위 테스트: 순수 자바 언어로만 테스트 하기. 단위 테스트가 훨씬 좋은 테스트이다. 단위별로 쪼개서 테스트를 잘 해야 한다.
  • 통합 테스트: 스프링 컨테이너와 데이터베이스를 이용한 테스트. 

스프링 JdbcTemplate

  • 순수 JDBC와 동일하게 환경설정(build.gradle, resources/application.properties)을 한다
  • 스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해준다 하지만 쿼리문은 직접 작성해야 한다.
  • 생성자가 하나일 경우 @Autowired를 생략할 수 있다.
  •  마이바티스가 더 익숙해서 낯선 감이 있지만 그래도 꼭 JdbcTemplate 공부해야함

JPA

  • JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다. JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있고 개발 생산성을 크게 높일 수 있다.
  • build.gradle
    dependencies {
    	...
    //	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    	runtimeOnly 'com.h2database:h2'
        ...
    }
  • resources/application.properties
    spring.jpa.show-sql=true
    spring.jpa.hibernate.ddl-auto=none
    • show-sql: JPA가 생성하는 SQL을 출력한다
    • ddl-auto: JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none을 사용하면 해당 기능을 사용하지 않을 수 있다. create를 사용하면 엔티티 정보를 바탕으로 create문을 직접 생성해준다
  • 도메인 클래스
    • JPA는 ORM(Object Relational Mapping)기술로 객체와 데이터베이스의 테이블을 매핑한다 Domain 클래스에 @Entity 어노테이션을 사용한다
    • @Id 어노테이션을 사용해 기본키를 매핑한다
    • 디비에서 자동으로 기본키 값을 주는 경우 @GeneratedValue(strategy = GenerationType.IDENTITY)로 지정한다
    • 필드명과 컬럼명이 다를 경우 @Column(name="컬럼명")로 매핑할 수 있다.
  • 리포지토리 클래스
    • EntityManager: JPA의 핵심 인터페이스 중 하나로, 데이터베이스와의 상호작용을 관리하는 역할을 한다. EntityManager를 사용해 영속성 컨텍스트를 관리하고, 엔티티의 영구 저장과 조회 등의 작업을 수행할 수 있다.
    • persist(): 엔티티를 영속 컨텍스트에 저장하는 메서드로, 메모리에서 관리되는 상태가 되며 이후 트랜잭션이 커밋될 떄 데이터베이스에 저장된다 아래처럼 쓰면 됨 걍. . . 
      @Override
      public Member save(Member member) {
      	em.persist(member);
      	return member;
      }
    • find(): 주어진 엔티티 클래스와 기본키 값을 이용해 엔티티를 데이터베이스에서 조회하는 메서드
      @Override
      public Optional<Member> findById(Long id) {
      	Member member = em.find(Member.class, id);
      	return Optional.ofNullable(member);
      }
    • createQuery(): JPQL(Java Persistence Query Language) 쿼리를 생성하는 메서드. JPQL은 엔티티를 대상으로 쿼리를 작성할 수 있는 객체 지향 쿼리 언어
      @Override
      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();
      }
      
      @Override
      public List<Member> findAll() {
      	return em.createQuery("select m from Member m", Member.class).getResultList();
      }
      • 'select m from Member m where m.name = :name'이 JPQL의 예로, 이 쿼리문은 Member라는 엔티티 클래스를 대상으로 조건에 맞는 엔티티를 조회하는 것을 나타낸다
        • select m: 조회할 엔티티 객체를 지칭하기 위해 m이라는 별칭을 사용한다. 이는 조회한 엔티티 객체를 나타내는데 사용된다
        • from Member m: Member라는 엔티티 클래스를 조회 대상으로 지정한다. 엔티티 클래스명은 JPQL에서 해당 엔티티를 식별하는데 사용된다. 별칭 m은 이후 쿼리에서 해당 엔티티를 참조할 떄 사용한다.
        • where m.name = :name: 조회할 엔티티 객체 중에서 name 필드가 :name으로 지정한 값과 같을(=) 경우를 필터링한다. 여기서 :name은 JPQL의 매개변수로, 실제로 쿼리를 실행할 떄 값이 주입된다. preparedStatement의 물음표같은 건가? 이렇게 하면 name 필드가 지정한 값과 동일한 엔티티만 조회되게 된다.

스프링 데이터 JPA

  • 스프링 부트와 JPA만 사용해도 개발 생산성이 정말 많이 증가하고, 개발해야할 코드도 확연히 줄어든다. 여기에 스프링 데이터 JPA를 사용하면, 기존의 한계를 넘어 마치 마법처럼, 리포지토리에 구현 클래스 없이 인터페ㅣ스 만으로 개발을 완료할 수 있다. 그리고 반복 개발해온 기본 CRUD기능도 스프링 데이터 JPA가 모두 제공한다
  • 스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술이므로 JPA를 먼저 학습한 후에 스프링 데이터 JPA를 학습해야 한다.
  • JPA를 위해 설정한 build.gradle와 resources/application.properites를 그대로 따른다
  • 너무 자동이라 당황스럽다 .. . . . 아무튼 JpaRepository<도메인클래스, PK자료형> 인터페이스와 리포지토리 클래스를 위한 MemberRepository 인터페이스를 상속받는 인터페이스 SpringDataJpaMemberRepository를 만들고 MemberRepository의 메서드를 작성한다 그리고 @Configuration 클래스에 다음과 같이 정의한다 쿼리문은 아무데도 정의하지 않았는데 알아서 저장이 된다 뭐지
    @Configuration
    public class SpringConfig {
    	private final MemberRepository memberRepository;
    
    	public SpringConfig(MemberRepository memberRepository) {
    		this.memberRepository = memberRepository;
    	}
    
    	@Bean
    	public MemberService memberService() {
    		return new MemberService(memberRepository);
    	}
    }
  • 아무튼.. 스프링 데이터 JPA가 JpaRepository 인터페이스를 상속받은 SpringDataJpaMemberRepository 인터페이스를 스프링 빈으로 자동 등록한다 JpaRepository의 상속관계를 보면 아래 사진과 같다 (차이가 있을 수 있음) 기본적인 CRUD를 위한 메서드와 페이징 및 정렬을 위한 메서드도 정의돼있다 
  • 위 사진에 없는 역할을 하는 메서드가 필요할 경우 findBy__ 로 메서드명을 작성하면 된다 그러니까 명명 규칙이 있다 만약 findByName이라면 select m from Member m where m.name = ?와 같은 행동을 하게 된다. . . 개쩜

 

 

728x90
반응형