It's easy, if you try
[Spring] 스프링 입문 강의 섹션 5, 6, 7 - 웰컴페이지/ DB 접근기술/ AOP 본문
웰컴페이지 우선순위
우선 스프링 컨테이너에서 관련 컨트롤러를 찾고, 없으면 static에서 찾기 때문에
- @GetMapping("/")
- static 내의 웰컴페이지로 설정한 파일
html
<form action="/members/new" method="post">
<div class="form-group">
<label for="name">이름</label>
<input type="text" id="name" name="name" placeholder="이름을 입력하세요">
</div>
</form>
- action url로 controller의 @PostMapping으로 매핑된다.
thymeleaf
<div class="container">
<div>
<thead>
<tr>
<th>#</th>
<th>이름</th>
</tr>
</thead>
<tbody>
<tr th:each="member : ${members}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
</tr>
</tbody>
</div>
</div>
th: 로 시작하는게 타임리프 문법
스프링 DB 접근 기술
1. H2 DB를 이용한 스프링 데이터 엑세스
개발이나 테스트 용도로 가볍고 편리한 DB, 웹 화면 제공
2. JDBC
과거에 사용하던 방식
스프링 통합 테스트
@SpringBootTest: 스프링 컨테이너와 테스트를 함께 실행한다.
@Transactional : 테스트 시작 전에 트랜잭션을 시작하고, 테스트가 끝나면 rollback 해준다. (다음 테스트에 영향을 주지 않는다)
스프링 JdbcTemplate
- 실무에서 많이 쓰인다.
- 순수 Jdbc와 동일한 환경설정을 하면 된다.
- 스프링 Jdbctemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드르 대부분 제거해주지만, SQL은 직접 작성해야한다.
- 생성자로 의존성 주입
@Configuration
public class SpringConfig {
private final JdbcTemplate jdbcTemplate;
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
}
JPA
기존의 반복코드와 기본적은 SQL을 모두 만들어서 실행해준다.
- SQL과 데이터 중심 -> 객체 중심의 설계
- 개발생산성 향상
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-jdbc'
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 관련 라이브러리를 포함한다.
resources/application.properties에 JPA 설정 추가
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
- show-sql : JPA가 생성하는 SQL을 호출한다.
- ddl-auto: none인 경우 테이블 자동 생성 기능을 끈다. (create로 설정 시 엔티티 정보를 바탕으로 JPA가 테이블도 직접 생성)
1) JPA 엔티티 매핑
- @Entity : 클래스 위에 선언
- @Id : 구분자 위에 선언
- @GeneratedValue(strategy = GenerationType.IDENTITY) : Id 어노테이션 옆에 선언
- getter, setter 있어야함.
2) JPA 회원 리포지토리
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaMemberRepository implements MemberRepository {
private final EntityManager em;
public JpaMemberRepository(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); //id로 select
return Optional.ofNullable(member); // null이어도 객체로 감싸서 반환
}
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(); // 찾은 요소들 중 가장 먼저 탐색되는 요소 리턴
//findFirst : 스트림에서 순서가 가장 앞에 있는 요소
}
}
3) 서비스 계층에 트랜잭션 추가
- @Transactional : org.springframework.transaction.annotation.Transactional
- 런타임 예외 발생 시 롤백.
- JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
스프링 데이터 JPA
스프링부트 + JPA 기반 위에 더해지는 프레임워크
개발 코드들이 확연히 줄어든다. -> 개발자는 핵심 비즈니스 로직을 개발하는데 집중할 수 있음.
1) 리포지토리를 만든다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface SpringDataJpaMemberRepository extends JpaRepository<Member,
Long>, MemberRepository {
Optional<Member> findByName(String name);
}
2) 스프링 데이터 JPA 회원 리포지토리를 사용하도록 스프링 설정을 변경한다.
package hello.hellospring;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.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가 SpringDataJpaMemberRepository 를 스프링 빈으로 자동 등록해준다.
- 인터페이스를 통한 기본적인 CRUD
- 메서드 이름 만으로 조회 기능 제공 (ex: findByName())
- 페이징 기능 자동 제공
실무에서는 JPA + 스프링 데이터 JPA를 기본으로 사용
복잡한 동적 쿼리는 Querydsl 라이브러리 또는 JPA가 제공하는 네이티브 쿼리 또는 JdbcTemplate.
Querydsl은 자바코드로 쿼리를 작성할 수 있고, 동적 쿼리를 편리하게 작성 가능.
AOP
1) AOP란 ?
Aspect Oriented Programming의 약자로 관점 지향 프로그래밍을 의미한다.
AOP를 통해 Aspect를 모듈화할 수 있다. (= 코드를 수정하지 않으면서 공통 기능의 구현을 추가하는 것)
2) AOP가 필요한 상황은 ?
- 모든 메소드의 호출 시간을 측정하고 싶을 때
- 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)
- 회원 가입 시간, 회원 조회 시간을 측정하고 싶을 때 등
3) AOP 구현 방법
- 컴파일: 자바 파일을 클래스 파일로 만들 때, 바이트코드를 조작하여 적용된 바이트코드를 생성
- 로드 타임: 컴파일은 원래 클래스대로 하고, 클래스를 로딩하는 시점에 끼워서 넣는다.
- 런타임: A라는 클래스를 빈으로 만들때, A라는 타입의 프록시 빈을 감싸서 만든 후에, 프록시 빈이 클래스 중간에 코드를 추가해서 넣는다.
주로 런타임 방법을 많이 이용한다. 스프링 AOP는 프록시 객체를 자동으로 만들어준다.
4) AOP 적용 전 vs 후
적용 전 회원 조회 시간 측정 추가
package hello.hellospring.service;
@Transactional
public class MemberService {
/**
* 회원가입
*/
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 적용 후 회원 조회 시간 측정 추가
공통 관심 사항과 핵심 관심 사항 분리
package hello.hellospring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
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");
}
}
}
- @Aspect: 공통 관심 사항을 모듈로 분리
- @Around
- excution을 사용하여 적용 범위를 지정 가능
hello.hellospring 패키지 하위 모든 클래스에 적용 - @annotation(어노테이션명)
어노테이션명이 달린 메소드가 실행되면 aspect의 around가 실행된다.
- excution을 사용하여 적용 범위를 지정 가능
- ProceedingJoinPoint / proceed() : proceed() 메소드를 사용해서 실제 대상 객체의 메소드를 호출하는 것.
- @Retention(RetentionPolicy.RUNTIME): default는 CLASS
참고
- [Spring] AOP(Aspect Oriented Programming)란 무엇일까?
- 김영한 스프링 MVC 입문 강의 자료
'스프링' 카테고리의 다른 글
[SpringBoot] 스프링부트와 JPA 활용 섹션 3, 4 - 어플리케이션 아키텍처/ 개발순서 / 엔티티, 리포지토리, 서비스, 테스트 작성 (1) | 2023.02.19 |
---|---|
[SpringBoot] 스프링부트와 JPA 활용 섹션 2 - 도메인 분석 설계 / Entity 클래스 작성 (1) | 2023.02.15 |
[Spring] 스프링 입문 강의 섹션 3 - 웹 어플리케이션 계층 구조 / 도메인, 리포지토리, 테스트케이스 작성 (0) | 2023.01.19 |
[Spring] 스프링 입문 강의 섹션 1,2 - 프로젝트 초기 세팅 / 라이브러리 / 웹 개발 기초(구조) (0) | 2023.01.12 |
[SpringBoot] 스프링부트와 JPA 활용 섹션 1- 라이브러리 의존과 Thymeleaf (0) | 2023.01.05 |