스프링 부트, 웹 MVC, DB

x·2023년 2월 8일
0

spring

목록 보기
2/3

프로젝트 생성

java 11 설치

intelliJ 설치

스프링 프로젝트를 시작할 수 있게 스프링 부트 스타터 사이트로 이동

start.spring.io

스프링 부트 기반으로 스프링 프로젝트를 만들 수 있게 해주는 사이트

maven, gradle : 라이브러리 설치하고 빌드하는 라이프사이클을 관리해주는 툴. 과거는 maven 썼고 요즘은 gradle을 사용함

group에 보통 기업명 입력함

artifact : 빌드된 후 나오는 결과물

dependencies : 어떤 라이브러리를 쓸 건지

spring web, thymeleaf

intelliJ에서 프로젝트 열면 라이브러리가 다운로드 됨

src 디렉토리에 main, test 디렉토리가 나뉘어 있음

resources는 자바 파일을 제외한 파일들을 담는 디렉토리

build.gradle에서 버전 설정, 라이브러리 관리 등을 한다

dependencies 에 작성된 라이브러리들을 mavenCentral 에서 다운로드 받음

gradlew, gradle.bat는 빌드할 때 쓰임

HelloSpringApplication 파일에서 main 실행하고 localhost:8080 가면 결과 나옴

package hello.hellospring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloSpringApplication {

	public static void main(String[] args) {
		SpringApplication.run(HelloSpringApplication.class, args);
	}

}

HelloSpringApplication 클래스를 SpringApplication.run()에 넘기면 스프링 부트 앱이 실행됨. tomcat이라는 웹 서버를 내장하고 있어서 그걸 띄우면서 자체적으로 서버가 실행됨

설정에서 gradle을 intelliJ로 바꾸기. gradle을 통해서 실행하면 느릴 때가 있음

라이브러리 살펴보기

gradle, maven 같은 빌드 툴은 의존 관계를 관리해줌. 그래서 build.gradle에 작성한 라이브러리는 적지만 의존하는 라이브러리들이 많이 설치됨

controller

package hello.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {

    @GetMapping("hello")
    public String Hello(Model model){
        model.addAttribute("data", "hello!!");
        return "hello";
    }
}

GET hello 로 요청이 들어오면 GetMapping에서 hello가 매칭되는 걸 찾고 메서드 실행함

model에 key가 data, value가 hello!! 값을 넣어줌

return된 hello는 template의 이름과 매칭됨. hello 템플릿에 렌더링 됨

빌드, 실행

서버 중지하고 ./gradlew build 로 빌드

cd build/libs 로 이동

java -jar hello-spring-0.0.1-SNAPSHOT.jar 로 실행

빌드 폴더 삭제

./gradlew clean

웹개발

API 방식

package hello.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {

    @GetMapping("hello-string")
    @ResponseBody
    public String helloString(@RequestParam("name") String name) {
        return "hello" + name;
    }

    @GetMapping("hello-api")
    @ResponseBody
    public Hello helloApi(@RequestParam("name") String name) {
        Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }

    class Hello{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

http://localhost:8080/hello-string?name="hihihi"

hello"hihihi” 응답됨

@ResponseBody 가 있을 때 스프링은 HttpMessageConverter 에게 반환 값을 전달함. 문자열이면 StringConverter, 객체면 JsonConverter

회원 도메인, 리포지토리 만들기

Member 클래스, MemberRepository 인터페이스, MemoryMemberRepository 클래스 생성

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findByName(String name) {
        return store.values().stream().filter(member -> member.getName().equals(name)).findAny();
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }
}

테스트 코드 작성

JUnit 사용

/src/test/java/hello/hellospring/repository/MemoryMemberRepositoryTest.java

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

class MemoryMemberRepositoryTest {

    MemberRepository repository = new MemoryMemberRepository();

    @Test
    public void save() {
        Member member = new Member();
        member.setName("spring");

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
//        Assertions.assertEquals(member, result);
        Assertions.assertThat(member).isEqualTo(result);
    }
}

assertj 를 사용한다. 행위주도개발, BDD 스타일. 메서드 체이닝을 제공하기 때문에 읽기 쉽다. 모든 테스트 코드는 assertThat()
메서드에서 시작됨.

service 생성 후 command shift t 누르면 테스트 생성하는 창이 뜸

컴포넌트 스캔과 자동 의존관계 설정

스프링 빈은 스프링 컨테이너에 의해 관리되는 자바 객체(POJO)

멤버 컨트롤러가 서비스를 이용해서 조작하게 되는데 컨트롤러가 서비스에 의존하는 의존 관계가 생기게 됨. 이 작업을 spring 스럽게 해보기

@Controller 어노테이션을 붙이면 스프링 컨테이너가 MemberController 객체를 생성해서 컨테이너에 넣어 두고 관리한다. 이를 스프링 빈이 관리된다고 표현함

package hello.hellospring.controller;

import org.springframework.stereotype.Controller;

@Controller
public class MemberController {
    
}


인스턴스를 쓸 때 new 대신 컨테이너에 있는 빈을 쓴다.

@Autowired 어노테이션이 있으면 스프링 컨테이너가 뜰 때 컨트롤러가 생성되는데 생성자를 호출하게 되고 의존성 주입된 인스턴스를 컨테이너에서 가져오게 된다.

Could not autowire. No beans of 'MemberService' type found. 이 에러가 뜨는 이유는 service class에 @Service 어노테이션이 없어서 스프링이 서비스를 찾지 못하기 때문이다

package hello.hellospring.controller;

import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class MemberController {

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
}

@Service 를 추가해서 컨테이너가 해당 서비스를 등록할 수 있게 해줌

@Service
public class MemberService {

여기서 컨테이너에 의한 의존성 주입이 일어남

@Autowired
public MemberController(MemberService memberService) {

스프링 빈을 등록하는 2가지 방법

  • 컴포넌트 스캔, 자동 의존관계 설정 : @Controller @Service 등 사용하는 방식. 이 안에 @Component 어노테이션이 달려있음. 컨테이너에 객체들이 등록되고 @Autowired로 연결됨.

아무 클래스에 컴포넌트 어노테이션을 붙이면 자바 빈 등록이 안될 수 있다.

HelloSpringApplication 파일 내 패키지의 하위에서 스프링이 컴포넌트들을 찾아 컨테이너에 등록한다.

  • 자바 코드로 직접 스프링 빈 등록하기

SpringConfig 클래스 생성

package hello.hellospring;

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;

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

@Controller를 제외한 나머지 @Service @Repository @Autowired 를 제거한다.

스프링이 @Bean 이 있는 걸 찾아서 컨테이너에 빈 등록을 한다.

스프링 DB

가벼운 H2 DB 설치

Downloads

/h2/bin 에서 chmod 755 he.sh

./h2.sh 실행

home에 test.mv.db 가 있어야 함

JDBC URL 을 jdbc:h2:tcp://localhost/~/test 로 변경. 파일에 직접 접근하는 게 아닌 소켓을 통해 접속해야 여러 곳에서 접속 가능

순수 JDBC로 앱 서버와 DB 연결

build.gradle 파일에 추가

implementation 'org.springframework.boot:spring-boot-starter-jdbc'

runtimeOnly 'com.h2database:h2'

application.properties에 추가

spring.datasource.url=jdbc:h2:tcp://localhost/~/test

spring.datasource.driver-class-name=org.h2.Driver

@Transactional 은 테스트가 끝나고 DB에 있는 걸 rollback함

@SpringBootTest 스프링 컨테이너와 테스트를 함께 실행함

좋은 테스트는 스프링 컨테이너를 띄워서 속도가 느린 테스트가 아니라 작은 단위의 속도가 빠른 테스트임

JdbcTemplate

JDBC API 반복 코드를 제거해줌. sql은 직접 작성해야 함

JPA

반복적 코드, 기본 sql을 JPA가 만들어서 실행함

sql과 데이터 중심 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있음

개발 생산성을 높일 수 있음

JPA는 인터페이스고 하이버네이트 등이 구현체

domain에 있는 모델에 entity 매핑해줘야 함

JPA는 EntityManager 로 모든 동작을 함. repository에서 주입해서 사용함

스프링 데이터 JPA

JPA를 편리하게 쓸 수 있게 한번 감싼거

CRUD 제공

스프링 부트와 JPA 기반에 스프링 데이터 JPA를 쓰면 코드가 줄어듦. 개발자는 핵심 비즈니스 로직을 개발하는데 집중할 수 있음

JPA를 학습하고 스프링 데이터 JPA를 학습해야 함

repository에 interface를 만들고 JpaRepository, MemberRepository를 상속받으면 스프링 데이터 JPA가 구현체를 만들고 스프링 빈에 등록함

JpaRepository에서 공통 CRUD는 제공하지만 다른 것들은 정의해야함.

메서드 이름을 잘 적으면 인터페이스만으로 개발이 끝남

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

    @Override
    Optional<Member> findByName(String name);
}

실무에서 JPA, 스프링 데이터 JPA를 사용하고 복잡한 동적 쿼리는 Querydsl이라는 라이브러리 사용함.

Querydsl을 사용하면 쿼리도 자바 코드로 작성할 수 있음

AOP

관점 지향 프로그래밍

공통 관심사항과 핵심 관심사항을 분리한다

AOP가 적용되면 스프링 컨테이너는 진짜 빈이 아닌 프록시를 호출하고 joinPoint.proceed()가 호출되면 실제 빈이 호출됨

0개의 댓글