본문 바로가기

Spring 공부

spring 공부 - JPA, AOP

728x90
반응형

 

회원가입기능

 

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="이름을입력하세요">
<!--            name 은 멤버에 있는 namd-->
        </div>
        <button type="submit">등록</button>
    </form>
</div> <!-- /container -->
</body>
</html>

 

 

controller


postMapping 어노테이션


return / 페이지

 

@PostMapping("/members/new")  // PostMapping
  public String create(MemberForm form) {
    Member member = new Member();
    member.setName(form.getName());
    memberService.join(member);
    return "redirect:/";  // redirect / 로 이동
  }

 

회원출력 기능

 

html
회원 목록 for each 처럼 꺼내서 하나씩 출력

 

<tbody>
<!--            for each처럼 하나씩 꺼내옴-->
            <tr th:each="member : ${members}">
                <td th:text="${member.id}"></td>
                <td th:text="${member.name}"></td>
            </tr>
            </tbody>

 

controller


Member 리스트 만들고
model.addAttribute "members"에 담아서 화면에넘김

 

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

 

스프링 컨테이너 변경

 

 

개방-폐쇄 원칙(OCP, Open-Closed Principle)
확장에는 열려있고, 수정, 변경에는 닫혀있다.
스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.
회원을 등록하고 DB에 결과가 잘 입력되는지 확인하자.
데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다.
회원을 등록하고 DB에 결과가 잘 입력되는지 확인하자.
데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다.

 

JPA

 

JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.
JPA를 사용하면 개발 생산성을 크게 높일 수 있다.

build.gradle에 추가

 

 

application.properties에 추가

 

 

JpaRepository

JPQL은 기본적으로 SQL을 추상화한 쿼리 언어이므로 SQL에서 제공해주는 DML(INSERT, UPDATE, SELECT, DELETE)을 EntityManager를 활용해서 사용할 수 있다.

em 생성: entity manager로 jpa 동작함
단건을 찾는건 자동으로해주는데 리스트같은 여러개는 sql 써줘야함
persist, find, em.createQuery

 

 

 

public class JpaMemberRepository implements MemberRepository {
  private final EntityManager em; // jpa는 entitymanager로 모든게 동작한다
  
  public JpaMemberRepository(EntityManager em) {
    this.em = em;
  }

  public Member save(Member member) { // insert 쿼리 다 만듦
    em.persist(member);  // persist 지속하다 영속하다
    return member;
  }
  public Optional<Member> findById(Long id) {
    Member member = em.find(Member.class, id);  // find 조회
    return Optional.ofNullable(member);
  }
  public List<Member> findAll() {
    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();
  }
}

 

 

SpringConfig

 

public class SpringConfig {

  private EntityManager em;

  @Autowired
  public SpringConfig(EntityManager em){
    this.em = em;
  }

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

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

 

JPA 테스트 코드

 

@Transactional 필요
선언전 트랜잭션이란 설정 파일 or 어노테이션 방식으로 간편하게 트랜잭션에 관한 행위를 정의하는 것

 

1.클래스, 메소드에 @Transactional이 선언되면 해당 클래스에 트랜잭션이 적용된 프록시 객체 생성
2.프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우, 트랜잭션을 시작하고 Commit or Rollback을 수행
3.CheckedException or 예외가 없을 때는 Commit
4.UncheckedException이 발생하면 Rollback

 

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("이미 존재하는 회원입니다.");
  }
}

 

스프링 데이터 JPA

스프링 부트와 JPA만 사용해도 개발 생산성이 증가, 개발해야할 코드도 줄어 듦
인터페이스만으로 개발 완료할 수 있다.
CRUD기능 스프링 데이터 JPA가 모두 제공

 

SpringDataJpaMemberRepository

 

인터페이스만 구현해도 Jpa레파지토리를 받고있으면 구현체를 자동으로 만들어 줌

 

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
  // MemberRepository 인터페이스 상속
  // 규칙이 있음 . findByName 해도 Name으로 찾음
  Optional<Member> findByName(String name);
}

 

SpringConfig

 

@Configuration: 설정파일을 만들기 위한 애노테이션 or Bean을 등록하기 위한 애노테이션

 

@Configuration
public class SpringConfig {
  private final MemberRepository memberRepository;
  @Autowired
  public SpringConfig(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
  }
  @Bean  // bean: 스프링 컨테이너에 의해 관리되는 하나의 객체
  public MemberService memberService() {
    return new MemberService(memberRepository);
  }
}

 

스프링 데이터 JPA가 SpringDataJpaMemberRepository 를 스프링 빈으로 자동 등록해준다.

 

findAll 같은거 기본 수정삭제 .. 미리 가지고 있음

 

 

인터페이스를 통한 기본적인 CRUD
findByName() , findByEmail() 처럼 메서드 이름 만으로 조회 기능 제공
페이징 기능 자동 제공

 

실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용하면 된다. Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할 수 있고, 동적 쿼리도 편리하게 작성할 수 있다. 이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, JdbcTemplate를 사용하면 된다

 

AOP가 필요한 상황

모든 메소드의 호출 시간 측정
공통 관심사항 vs 핵심 관심 사항
회원가입시간, 회원 조회 시간 측정

 

 

 

memberservice

 

start 시간, 끝나는 시간잰 후 , timeMs 에 끝시간 - 시작시간 해서 출력

 

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

AOP: Aspect Oriented Programming
공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 분리

 

 

TimeTraceAop

 

메서드 호출할때마다 인터셉트되서 이거 실행

 

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

 

config

 

  @Bean
  public TimeTraceAop TimeTraceAop(){
    return new TimeTraceAop();
 }

 

처음 켰을때:

 

 

회원목록 조회:

 

 

회원가입, 회원 조회등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다.
시간을 측정하는 로직을 별도의 공통 로직을 만들었다.
핵심 관심 사항을 깔끔하게 유지할 수 있다.
변경이 필요하면 이 로직만 변경하면된다.
원하는 적용 대상을 선택할 수 있다.

 

동작 방식

스프링 컨테이너가 AOP가적용되야하는거네 싶으면
가짜 memberService 만든다 joinPoint.proceed() 후 진짜로 간다

 

 

 

 

 

 

 

 

 

 

 

 

반응형