๐Ÿ“‹ ์Šคํ”„๋ง ์ž…๋ฌธ - ์ฝ”๋“œ๋กœ ๋ฐฐ์šฐ๋Š” ์Šคํ”„๋ง ๋ถ€ํŠธ, ์›น MVC, DB ์ ‘๊ทผ ๊ธฐ์ˆ  ๊ฐ•์˜ ๋…ธํŠธ


3. ํšŒ์› ๊ด€๋ฆฌ ์˜ˆ์ œ - ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ


๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ

  • ๋ฐ์ดํ„ฐ : ํšŒ์› ID, ์ด๋ฆ„
  • ๊ธฐ๋Šฅ : ํšŒ์› ๋“ฑ๋ก, ์กฐํšŒ
  • ์•„์ง ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๊ฐ€ ์„ ์ •๋˜์ง€ ์•Š์Œ

์ผ๋ฐ˜์ ์ธ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ณ„์ธต ๊ตฌ์กฐ

  • ์ปจํŠธ๋กค๋Ÿฌ : ์›น MVC์˜ ์ปจํŠธ๋กค๋Ÿฌ ์—ญํ• 
  • ์„œ๋น„์Šค : ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ตฌํ˜„
  • ๋ ˆํฌ์ง€ํ† ๋ฆฌ : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผ, ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ DB์— ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌ
  • ๋„๋ฉ”์ธ : ๋น„์ฆˆ๋‹ˆ์Šค

ํด๋ž˜์Šค ์˜์กด ๊ด€๊ณ„

image

ํšŒ์›์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ Repository ์ธํ„ฐํŽ˜์ด์Šค.
์ผ๋‹จ์€ ๋ฉ”๋ชจ๋ฆฌ ๊ตฌํ˜„์ฒด.
DBMS๋ฅผ ์ž์œ ๋กญ๊ฒŒ ๋ฐ”๊ฟ” ๋ผ์šฐ๊ธฐ ์œ„ํ•ด ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ตฌํ˜„ํ•˜์˜€์Œ.

Member Domain

public class Member {
    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;
    }
}

MemberRepository

public interface MemberRepository {
    Member save(Member member);
    Optional<Member> findById(Long id);
    Optional<Member> findByNames(String name);
    List<Member> findAll();
}
  • findById(), findByNames() ์€ null์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ Optional๋กœ ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

MemoryMemberRepository

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)); // null์ด์–ด๋„ ๊ฐ์Œ€ ์ˆ˜ ์žˆ๋‹ค. ํด๋ผ์—์„œ ๋ฌด์–ธ๊ฐˆ ํ•  ์ˆ˜ ์žˆ
    }

    @Override
    public Optional<Member> findByNames(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny(); // ๋ฃจํ”„๋ฅผ ๋Œ๋ฉด์„œ ์—†์œผ๋ฉด optional์— null์ด ํฌํ•จ๋˜์„œ ๋ฐ˜ํ™˜
    }

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

MemoryMemberRepositoryTest

public class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

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

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        assertThat(member).isEqualTo(result);
    }

    @Test
    void findByName() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByNames("spring1").get();

        assertThat(result).isEqualTo(member1);
    }
  
  @Test
    void findAll() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);
    }
}

๋ชจ๋“  ํ…Œ์ŠคํŠธ๋Š” ๋ฉ”์„œ๋“œ๋งˆ๋‹ค ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค.
์ˆœ์„œ์— ์˜์กด๋„๊ฐ€ ์ƒ๊ธฐ๋ฉด ์•ˆ๋œ๋‹ค.

@AfterEach
void afterAll() {
    repository.clearStore();
}

๋•Œ๋ฌธ์— ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋  ๋•Œ ๋งˆ๋‹ค ์ดˆ๊ธฐํ™”๋ฅผ ์ง„ํ–‰ํ•ด์ค€๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์—†์ด ๊ฐœ๋ฐœ์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

MemberService

public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();

    public long join(Member member) {
        validateDuplicatedMember(member);

        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicatedMember(Member member) {
        // ์ค‘๋ณต ํšŒ์›์€ ์•ˆ๋œ๋‹ค.
        memberRepository.findByNames(member.getName())
                .ifPresent(m -> {
                    throw new IllegalArgumentException("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค.");
                });
    }

    public List<Member> findMembers() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

ํ•˜์ง€๋งŒ Optional์„ ๋ฐ”๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์ง€ ์•Š๋‹ค.

// ์ค‘๋ณต ํšŒ์›์€ ์•ˆ๋œ๋‹ค.
memberRepository.findByNames(member.getName())
  .ifPresent(m -> {
    throw new IllegalArgumentException("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค.");
  });

ifPresent() ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ”๋กœ ํ™•์ธ.

  • ์„œ๋น„์Šค ํด๋ž˜์Šค๋Š” ๋น„์ฆˆ๋‹ˆ์Šค์ ์ธ ๋„ค์ด๋ฐ์„ ์จ์•ผํ•œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ์™€ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ํ…Œ์ŠคํŠธ์—์„œ ๊ฐ๊ฐ์˜ MemoryMemberRepository ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ๋‹ค.
ํ˜„์žฌ๋Š” static์œผ๋กœ ์„ ์–ธ๋˜์–ด ์žˆ์ง€๋งŒ, ์ด์น˜์— ๋งž์ง€ ์•Š๋Š” ํ˜„์ƒ.
์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€์—์„œ ์ฃผ์ž…ํ•˜๋„๋ก ํ•œ๋‹ค.

์ƒ์„ฑ์ž ์ฃผ์ž…(DI)

@BeforeEach
void beforeEach() {
    memberRepository = new MemoryMemberRepository();
    memberService = new MemberService(memberRepository);
}

๊ฐ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „์— ํ˜ธ์ถœ๋˜๋ฉฐ ํ…Œ์ŠคํŠธ๊ฐ€ ์„œ๋กœ ์˜ํ–ฅ ์—†์ด ํ•ญ์ƒ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์˜์กด๊ด€๊ณ„๋„ ์ƒˆ๋กœ ๋งบ์–ด์ค€๋‹ค.

Controller ์—์„œ ์š”์ฒญ์„ ๋ฐ›๊ณ , Service์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๊ณ , Repository์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ์ด๊ฒŒ ์ •ํ˜•ํ™”๋œ ๋กœ์ง์ด๋‹ค.

์Šคํ”„๋ง์ด ๊ด€๋ฆฌ๋ฅผ ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํฌํ•จ๋œ ํ•˜์œ„ ํŒจํ‚ค์ง€ ๋‚ด์—์„œ ์Šคํ”„๋ง์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์Šค์บ”ํ•œ๋‹ค.
์Šคํ”„๋ง์€ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์— ์Šคํ”„๋ง ๋นˆ์„ ๋“ฑ๋กํ•  ๋•Œ, ๊ธฐ๋ณธ์œผ๋กœ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค. (๋ฉ”๋ชจ๋ฆฌ๋„ ์ ˆ์•ฝ๋œ๋‹ค.)

4. ์Šคํ”„๋ง ๋นˆ๊ณผ ์˜์กด๊ด€๊ณ„


์ปดํฌ๋„ŒํŠธ ์Šค์บ”๊ณผ ์ž๋™ ์˜์กด๊ด€๊ณ„ ์„ค์ •

์ƒ์„ฑ์ž์— @Autowired๊ฐ€ ์žˆ์œผ๋ฉด ์Šคํ”„๋ง์ด ์—ฐ๊ด€๋œ ๊ฐ์ฒด๋ฅผ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์—์„œ ์ฐพ์•„์„œ ๋„ฃ์–ด์ค€๋‹ค.
๊ฐ์ฒด ์˜์กด ๊ด€๊ณ„๋ฅผ ์™ธ๋ถ€์—์„œ ๋„ฃ์–ด์ฃผ๋Š” ๊ฒƒ์„ DI๋ผ๊ณ  ํ•œ๋‹ค.

์Šคํ”„๋ง ๋นˆ์„ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ์ปดํฌ๋„ŒํŠธ ์Šค์บ”๊ณผ ์ž๋™ ์˜์กด๊ด€๊ณ„ ์„ค์ •
  • ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์ง์ ‘ ์Šคํ”„๋ง ๋นˆ ๋“ฑ๋กํ•˜๊ธฐ

@Component ๊ฐ€ ์žˆ์œผ๋ฉด ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ์ž๋™ ๋“ฑ๋ก๋œ๋‹ค. (@Controller, @Service, @Repository). ์ƒ์„ฑ์ž์— @Autowired ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ์ฒด ์ƒ์„ฑ ์‹œ์ ์— ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์—์„œ ํ•ด๋‹น ์Šคํ”„๋ง ๋นˆ์„ ์ฐพ์•„์„œ ์ฃผ์ž…ํ•œ๋‹ค.
์ƒ์„ฑ์ž๊ฐ€ 1๊ฐœ๋งŒ ์žˆ์œผ๋ฉด @Autowired ๋Š” ์ƒ๋žต ๊ฐ€๋Šฅํ•˜๋‹ค.

์Šคํ”„๋ง์€ ์Šคํ”„๋ง ๋นˆ์„ ๋“ฑ๋กํ•  ๋•Œ ๊ธฐ๋ณธ์œผ๋กœ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค. (์œ ์ผํ•˜๊ฒŒ ํ•˜๋‚˜๋งŒ ๋“ฑ๋ก).
๋”ฐ๋ผ์„œ ๊ฐ™์€ ์Šคํ”„๋ง ๋นˆ์ด๋ฉด ๋ชจ๋‘ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๋‹ค.

@Autowired ๋ฅผ ํ†ตํ•œ DI๋Š” ์Šคํ”„๋ง์ด ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด(๋นˆ)์—์„œ๋งŒ ๋™์ž‘ํ•œ๋‹ค.

DI์—๋Š” ํ•„๋“œ ์ฃผ์ž…, setter ์ฃผ์ž…, ์ƒ์„ฑ์ž ์ฃผ์ž… 3๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ,
์˜์กด ๊ด€๊ณ„๊ฐ€ ๋™์ ์œผ๋กœ ๋ณ€ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ๊ฑฐ์˜ ์—†์œผ๋ฏ€๋กœ ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ๊ถŒ์žฅํ•œ๋‹ค.

5. ํšŒ์› ๊ด€๋ฆฌ ์˜ˆ์ œ - ์›น MVC ๊ฐœ๋ฐœ


์š”์ฒญ์ด ์˜ฌ ๊ฒฝ์šฐ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์—์„œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ฐพ๊ณ  ์—†์œผ๋ฉด static ํŒŒ์ผ์„ ์ฐพ๋Š”๋‹ค.

6. ์Šคํ”„๋ง DB ์ ‘๊ทผ ๊ธฐ์ˆ 


jdbc๋งŒ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์ค‘๋ณต์ด ๋„ˆ๋ฌด ๋งŽ์œผ๋‹ˆ spring์—์„œ๋Š” jdbc templates๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

sql์„ ๋‹ค๋ฃจ์ง€ ์•Š๊ณ , ์ฟผ๋ฆฌ ์—†์ด ๊ฐ์ฒด๋ฅผ db์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

jpa๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ๊ฐ์‹ผ ๊ฒƒ์ด ์Šคํ”„๋ง JPA

SOLID - ๊ฐœ๋ฐฉ ํ์‡„ ์›์น™(OCP)

ํ™•์žฅ์—๋Š” ์—ด๋ ค์žˆ๊ณ , ์ˆ˜์ •๊ณผ ๋ณ€๊ฒฝ์—๋Š” ๋‹ซํ˜€์žˆ๋‹ค.
์Šคํ”„๋ง์˜ DI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ์กด ์ฝ”๋“œ๋Š” ์ „ํ˜€ ์†๋Œ€์ง€ ์•Š๊ณ , ์„ค์ •๋งŒ์œผ๋กœ ๊ตฌํ˜„ ํด๋ž˜์Šค๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

์Šคํ”„๋ง ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

์ง€๊ธˆ๊นŒ์ง€๋Š” ์ˆœ์ˆ˜ ์ž๋ฐ” ์ฝ”๋“œ๋งŒ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋Š”๋ฐ,
์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ ๋ชจ๋“  ์ •๋ณด๊ฐ€ ๋“ค๊ณ  ์žˆ์œผ๋‹ˆ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์™€ DB๊นŒ์ง€ ์—ฐ๊ฒฐํ•œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.

์Šคํ”„๋ง ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋Š” DI๊ฐ€ ์•„๋‹Œ ํ•„๋“œ ์ธ์ ์…˜์„ ์‚ฌ์šฉํ•œ๋‹ค.
ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค๋ฅธ๋ฐ์„œ๋„ ์“ฐ์ด๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ•œ ํด๋ž˜์Šค ๋‚ด์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ๋์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

@SpringBootTest
@Transactional
public class MemberServiceIntegrationTest {
    @Autowired
    MemberService memberService;
    @Autowired
    MemberRepository memberRepository;

    @Test
    void ํšŒ์›๊ฐ€์ž…() {
        // given
        Member member = new Member();
        member.setName("hello");

        // when
        Long saveId = memberService.join(member);

        // then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    void ์ค‘๋ณตํšŒ์›์˜ˆ์™ธ() {
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        // when
        memberService.join(member1);

        // then
        assertThatThrownBy(() -> {
            memberService.join(member2);
        }).isInstanceOf(IllegalArgumentException.class);

        IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> memberService.join(member2));
        assertThat(e.getMessage().equals("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค.")).isTrue();
    }
}
  • @SpringBootTest : ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์™€ ํ…Œ์ŠคํŠธ๋ฅผ ํ•จ๊ป˜ ์‹คํ–‰ํ•œ๋‹ค.
  • @Transactional : ๊ฐ๊ฐ์˜ ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ์ „์— ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ , ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ํ›„์— ํ•ญ์ƒ ๋กค๋ฐฑํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด DB์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚จ์ง€ ์•Š์œผ๋ฏ€๋กœ ๋‹ค์Œ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค
  • @Commit : ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ๋๋‚˜๋ฉด ์ปค๋ฐ‹ํ•œ๋‹ค.

JDBC Templates

JDBC Templates๋ฅผ ๋ฐ”๋กœ ์ฃผ์ž…๋ฐ›์„ ์ˆ˜ ์—†๋‹ค.

public JdbcTemplateMemberRepository(DataSource dataSource) {
    jdbcTemplate = new JdbcTemplate(dataSource);
}

JDBC Template์€ ํ…œํ”Œ๋ฆฟ ๋ฉ”์„œ๋“œ ํŒจํ„ด์ด๋‹ค.

@Override
public Optional<Member> findById(Long id) {
    List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
    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;
  };
}

query ์ˆ˜ํ–‰ ๊ฒฐ๊ณผ๋Š” RowMapper๋กœ ๋งตํ•‘์„ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.
๊ฐ์ฒด ์ƒ์„ฑ์€ RowMapper์—์„œ ํ•œ๋‹ค.

JPA

๊ธฐ์กด์˜ ๋ฐ˜๋ณต ์ฝ”๋“œ๋Š” ๋ฌผ๋ก , SQL๋„ JPA๊ฐ€ ์ง์ ‘ ๋งŒ๋“ค์–ด์„œ ์‹คํ–‰ํ•ด์ค€๋‹ค.
SQL๊ณผ ๋ฐ์ดํ„ฐ ์ค‘์‹ฌ์˜ ์„ค๊ณ„์—์„œ ๊ฐ์ฒด ์ค‘์‹ฌ์˜ ์„ค๊ณ„๋กœ ํŒจ๋Ÿฌ๋‹ค์ž„์„ ์ „ํ™˜ ํ•  ์ˆ˜ ์žˆ๋‹ค.
JPA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์„ ํฌ๊ฒŒ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.
JPA๋Š” ๊ฐ์ฒด์™€ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค๋ฅผ ๋งตํ•‘ํ•˜๋Š” ORM์ด๋‹ค.

build.gradle

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}

spring-boot-starter-data-jpa ๋Š” ๋‚ด๋ถ€์— jdbc ๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํฌํ•จํ•˜๊ธฐ ๋•Œ๋ฌธ์— jdbc๋Š” ์ œ๊ฑฐํ•ด๋„ ๋œ๋‹ค.

JPA๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋‹ค. Hibernate๋Š” ์ด์˜ ๊ตฌํ˜„์ฒด์ด๋‹ค.

Member

@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;
    }
}

๋ฐ์ดํ„ฐ์˜ id๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๊ฒƒ์„ identity ์ „๋žต์ด๋ผ๊ณ  ํ•œ๋‹ค.

JpaMemberRepository

@Override
public List<Member> findAll() {
    return em.createQuery("select m from Member m", Member.class)
            .getResultList();
}
  • "select m from Member m" : jpql์ด๋ผ๋Š” ์ฟผ๋ฆฌ. ๊ฐ์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฐ๋‹ค. ์ž๋™์œผ๋กœ ์ฟผ๋ฆฌ๋ฌธ์ด ๋œ๋‹ค.

JPA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ญ์ƒ Transaction์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์„œ๋น„์Šค ๊ณ„์ธต์— ํŠธ๋žœ์žญ์…˜์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
์Šคํ”„๋ง์€ ํ•ด๋‹น ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ , ๋ฉ”์„œ๋“œ๊ฐ€ ์ •์ƒ ์ข…๋ฃŒ๋˜๋ฉด ํŠธ๋žœ์žญ์…˜์„ ์ปค๋ฐ‹ํ•œ๋‹ค. ๋งŒ์•ฝ ๋Ÿฐํƒ€์ž„ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋กค๋ฐฑํ•œ๋‹ค. JPA๋ฅผ ํ†ตํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์€ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค.

์Šคํ”„๋ง Data JPA

์Šคํ”„๋ง ๋ถ€ํŠธ์™€ JPA๋งŒ ์‚ฌ์šฉํ•ด๋„ ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์ด ์ •๋ง ๋งŽ์ด ์ฆ๊ฐ€ํ•˜๊ณ , ๊ฐœ๋ฐœํ•ด์•ผํ•  ์ฝ”๋“œ๋„ ํ™•์—ฐํžˆ ์ค€๋‹ค.
์—ฌ๊ธฐ์— ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ๊ตฌํ˜„ ํด๋ž˜์Šค ์—†์ด ์ธํ„ฐํŽ˜์ด์Šค ๋งŒ์œผ๋กœ ๊ฐœ๋ฐœ์„ ์™„๋ฃŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๋ฐ˜๋ณต ๊ฐœ๋ฐœํ•ด์˜จ ๊ธฐ๋ณธ CRUD ๊ธฐ๋Šฅ๋„ ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๊ฐ€ ๋ชจ๋‘ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ๊ฐœ๋ฐœ์ž๋Š” ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊ฐœ๋ฐœํ•˜๋Š”๋ฐ, ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์‹ค๋ฌด์—์„œ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋Š” ์ด์ œ ์„ ํƒ์ด ์•„๋‹ˆ๋ผ ํ•„์ˆ˜ ์ž…๋‹ˆ๋‹ค

์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋Š” JPA๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” ๊ธฐ์ˆ ์ด๋‹ค.

์Šคํ”„๋ง JPA๊ฐ€ ์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ๊ตฌํ˜„์ฒด๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด๋‚ธ๋‹ค.

๊ตฌํ˜„ ํด๋ž˜์Šค ์ž‘์„ฑ ํ•„์š” ์—†์ด ์ธํ„ฐํŽ˜์ด์Šค ์ด๋ฆ„ ๋งŒ์œผ๋กœ๋„ ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

์‹ค๋ฌด์—์„œ๋Š” JPA์™€ ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ JPA๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ๋ณต์žกํ•œ ๋™์  ์ฟผ๋ฆฌ๋Š” Querydsl์ด๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. Querydsl์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฟผ๋ฆฌ๋„ ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ณ , ๋™์  ์ฟผ๋ฆฌ๋„ ํŽธ๋ฆฌํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ์กฐํ•ฉ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ ์–ด๋ ค์šด ์ฟผ๋ฆฌ๋Š” JPA๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ์•ž์„œ ํ•™์Šตํ•œ ์Šคํ”„๋ง JdbcTemplate๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

7. AOP


ํ•„์š”ํ•œ ์ƒํ™ฉ

  • ๋ชจ๋“  ๋ฉ”์†Œ๋“œ์˜ ํ˜ธ์ถœ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๊ณ  ์‹ถ์„ ๋•Œ
  • ๊ณตํ†ต ๊ด€์‹ฌ ์‚ฌํ•ญ vs ํ•ต์‹ฌ ๊ด€์‹ฌ ์‚ฌํ•ญ
  • ํšŒ์› ๊ฐ€์ž… ์‹œ๊ฐ„, ํšŒ์› ์กฐํšŒ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๊ณ  ์‹ถ์„ ๋•Œ

๋ฉ”์„œ๋“œ ๋ณ„๋กœ ์ˆ˜ํ–‰ ์‹œ๊ฐ„์„ ์ฐ์–ด๋ณธ๋‹ค.

public Long join(Member member) {
   long start = System.currentTimeMillis();
   try {
     validateDuplicateMember(member); //์ค‘๋ณต ํšŒ์› ๊ฒ€์ฆ
     memberRepository.save(member);
     return member.getId();
     } finally {
       long finish = System.currentTimeMillis();
       long timeMs = finish - start;
       System.out.println("join " + timeMs + "ms");
   }
}
public List<Member> findMembers() {
   long start = System.currentTimeMillis();
   try {
   	 return memberRepository.findAll();
   } finally {
     long finish = System.currentTimeMillis();
     long timeMs = finish - start;
     System.out.println("findMembers " + timeMs + "ms");
   }
 }

๋ฌธ์ œ

  • ํšŒ์›๊ฐ€์ž…, ํšŒ์› ์กฐํšŒ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๊ธฐ๋Šฅ์€ ํ•ต์‹ฌ ๊ด€์‹ฌ ์‚ฌํ•ญ์ด ์•„๋‹ˆ๋‹ค.
  • ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง์€ ๊ณตํ†ต ๊ด€์‹ฌ ์‚ฌํ•ญ์ด๋‹ค.
  • ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง๊ณผ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค์˜ ๋กœ์ง์ด ์„ž์—ฌ์„œ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ต๋‹ค.
  • ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง์„ ๋ณ„๋„์˜ ๊ณตํ†ต ๋กœ์ง์œผ๋กœ ๋งŒ๋“ค๊ธฐ ๋งค์šฐ ์–ด๋ ต๋‹ค.
  • ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง์„ ๋ณ€๊ฒฝํ•  ๋•Œ ๋ชจ๋“  ๋กœ์ง์„ ์ฐพ์•„๊ฐ€๋ฉด์„œ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค.

AOP (Aspect Oriented Programming) ์ ์šฉ

๊ณตํ†ต ๊ด€์‹ฌ ์‚ฌํ•ญ๊ณผ ํ•ต์‹ฌ ๊ด€์‹ฌ ์‚ฌํ•ญ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.

@Aspect
@Component
public class TimeTraceAop {

    @Around("execution(* hello.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("START: " + joinPoint.toString());
        try {
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
        }
    }
}
  • ํšŒ์›๊ฐ€์ž…, ํšŒ์› ์กฐํšŒ๋“ฑ ํ•ต์‹ฌ ๊ด€์‹ฌ์‚ฌํ•ญ๊ณผ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๊ณตํ†ต ๊ด€์‹ฌ ์‚ฌํ•ญ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.
  • ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜๋Š” ๋กœ์ง์„ ๋ณ„๋„์˜ ๊ณตํ†ต ๋กœ์ง์œผ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค.
  • ํ•ต์‹ฌ ๊ด€์‹ฌ ์‚ฌํ•ญ์„ ๊น”๋”ํ•˜๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋ฉด ์ด ๋กœ์ง๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋œ๋‹ค.
  • ์›ํ•˜๋Š” ์ ์šฉ ๋Œ€์ƒ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค (@Around(โ€œexecution(* hello.hellospring.service.*(..))โ€))

AOP๊ฐ€ ์žˆ์œผ๋ฉด ํ”„๋ก์‹œ๋ฅผ ๋งŒ๋“ค์–ด ๊ฐ€์งœ ๋นˆ์„ ๋งŒ๋“ ๋‹ค.
๊ฐ€์งœ ๋นˆ์—์„œ joinPoint.proceed()๊ฐ€ ์ผ์–ด๋‚˜๋ฉด ์‹ค์ œ ๋นˆ์„ ํ˜ธ์ถœํ•ด์ค€๋‹ค.

image

image

์ฝ˜์†”์— ์ถœ๋ ฅํ•ด๋ณด๋ฉด ์‹ค์ œ MemberService๊ฐ€ ์•„๋‹Œ Proxy๊ฐ€ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ฐธ๊ณ  ์ž๋ฃŒ