Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch6
- 스프링 mvc2 - 로그인 처리
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch1
- 자바의 정석 기초편 ch9
- 코드로 시작하는 자바 첫걸음
- 게시글 목록 api
- 스프링 db2 - 데이터 접근 기술
- 스프링 mvc1 - 스프링 mvc
- 스프링 mvc2 - 검증
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch12
- 2024 정보처리기사 시나공 필기
- 스프링 입문(무료)
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch7
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch11
- 스프링 mvc1 - 서블릿
- 스프링 db1 - 스프링과 문제 해결
- 자바의 정석 기초편 ch8
- 자바의 정석 기초편 ch4
- @Aspect
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch3
Archives
- Today
- Total
나구리의 개발공부기록
스프링 DB 접근 기술 2 - 스프링 JdbcTemplate, JPA, 스프링 데이터 JPA / AOP - AOP가 필요한 상황, AOP적용 본문
인프런 - 스프링 완전정복 코스 로드맵/코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술(무료)
스프링 DB 접근 기술 2 - 스프링 JdbcTemplate, JPA, 스프링 데이터 JPA / AOP - AOP가 필요한 상황, AOP적용
소소한나구리 2024. 1. 24. 21:30스프링 JdbcTemplate
- 순수 JDBC와 동일한 환경설정을 진행
- JDBC API의 반복코드를 대부분 제거해주지만 SQL구문은 직접 입력해야 함
- JdbcTemplate
- MyBatis
- JdbcTemplateMemberRepository class 작성
public class JdbcTemplateMemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
// @Autowired(생성자가 1개만 있으므로 생략가능)
public JdbcTemplateMemberRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
// sql구문 없이 insert 구문을 구현하도록 코드 작성
// 도큐멘트 보고 작성해도 됨(자세히 나와있음)
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
member.setId((key.longValue()));
return member;
}
@Override
public Optional<Member> findById(Long id) {
List<Member> result = jdbcTemplate.query
("select * from member where id = ?", memberRowMapper());
return result.stream().findAny();
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query
("select * from member where name = ?", memberRowMapper());
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
private RowMapper<Member> memberRowMapper() {
// 람다로 변환이 가능(Option + 엔터)
// return new RowMapper<Member>() {
// @Override
// public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
//
// Member member = new Member();
// member.setId(rs.getLong("id"));
// member.setName(rs.getString("name"));
// return member;
// }
// 람다로 변환됨
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
}; // 세미콜론 추가
}
}
- SpringConfig class수정
@Configuration
public class SpringConfig {
.... 기타 코드들..
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
}
}
JPA
- JPA = 인터페이스, 자바의 표준 API로 구현은 각 업체들이 구현
- 구현체로 Hibernate, Eclipse Link 등의 벤더가 있으나 Hibernate를 거의 사용
- 기존의 반복코드는 물론 기본적인 SQL도 JPA가 직접 만들어서 실행 해줌
- JPA를 사용하면 SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있음
- JPA를 사용하면 개발 생산성을 크게 높일 수 있음
- JPA는 객체와 ORM 기술
- Object
- Relational
- Mapping
- build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 입력 - 입력 후 우측 상단 코끼리버튼 클릭(새로고침)
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // jpa,jdbc 전부 포함
- ~/resources/application.properties(스프링부트)에 JPA 설정 추가
// jpa가 날리는 sql을 볼 수 있음
spring.jpa.show-sql=true
// jpa를 사용하면 객체를 보고 테이블을 자동으로 만듦
// 지금은 테이블이 있으므로 none으로 설정
spring.jpa.hibernate.ddl-auto=none
// spring.jpa.hibernate.ddl-auto=create
- JpaMemberRepository class 작성
public class JpaMemberRepository implements MemberRepository {
// JPA는 EntityManager라는 것으로 모든 동작을 함
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
@Override
public Member save(Member member) {
em.persist(member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
// PK기반이 아닌 메서드들은 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() {
// List<Member> result = em.createQuery("select m from Member m", Member.class).getResultList();
// return result;
// 참조변수 값과 return값이 같을 경우 Command + Option + n 하면 합칠 수 있음
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
}
- JPA를 사용하려면 항상 @Transactional이 있어야 함(데이터를 저장하고 변경할 때)
- MemberService class @Transactional 추가
@Transactional
public class MemberService {
private final MemberRepository memberRepository;
....
}
// 이런식으로 필요한 곳에 해줘도 됨
@Transactional
public Long join(Member member) {
// 같은 이름이 있는 중복 회원 X
}
- JPA를 사용하려면 EntityManager라는 것이 필요함
- SpringConfig class 수정
@Configuration
public class SpringConfig {
// @PersistenceContext - 원래는 작성해야하지만 스프링에서 알아서 DI해줌
//JPA는 EntityManager가 필요함
private EntityManager em;
@Autowired
public SpringConfig(EntityManager em) {
this.em = em;
}
// Bean을 등록
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em);
}
}
스프링 데이터 JPA
- 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공
- 스프링부트 + JPA 위에 스프링 데이터 JPA 프레임워크를 더하면 개발이 즐거워지고 단순하고 반복적인 개발 코드들이 줄어듦
- 핵심 비즈니스로직을 개발하는데 집중할 수 있음
- 관계형 데이터베이스를 사용하면 스프링 데이터 JPA는 필수가 되고 있음
주의
- 스프링 데이터 JPA는 JPA편리하게 사용하게 도와주는 기술이므로 JPA를 먼저 학습한 후에 스프링 데이터 JPA학습을 해야 함
- 앞의 JPA 설정을 그대로 사용
- SpringDataJpaRepository interface 작성
package start.startspring.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import start.startspring.domain.Member;
import java.util.Optional;
// 스프링 데이터 JPA가 스스로 구현체를 만들어서 스프링 빈에 등록함
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
// JPQL이 자동으로 작성되어 SQL로 번역되어 실행 됨,
// Select m from Member m where m.name = ?
@Override
Optional<Member> findByName(String name);
}
- SpringConfig class 수정
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
@Autowired // 생성자가 1개여서 생략 가능
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
// Bean을 등록
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
}
스프링 데이터 JPA 제공 기능
- JpaRepository 인터페이스를 들어가보면 여러 Repository 인터페이스들을 상속하여 기본적인 CRUD, 단순 조회하는 다양한 메서드들이 제공
최고조상 | Repository Interface | ||
interface | JpaRepository | PagingAndSortingRepository | CrudRepository |
method | findAll() : List<T> findAll(Sort) : List<T> findAll(Iterable<ID> : List<T> |
findAll(Sort) : Iterable<T> findAll(Pageable) : Page<T> |
findOne(ID) : T |
save(Iterable<S>) : List<S> saveAndFlush(T) : T |
save(S) : S | ||
flush() | exists(ID) : boolean | ||
deleteInBatch(Iterable<T>) deleteAllInBatch() |
delete(T) | ||
getOne(ID) : T | count() : long |
- 위 메서드 외에 공통화 할 수 있는 것들은 메서드로 거의 구현 해놓았으니 실제로는 한번 더 찾아볼 것을 권장
- 해당 메서드들을 오버라이딩 해서 class를 작성하면 편리하게 구현이 가능함
- 문자열(name 같은)것은 비즈니스마다 사용하는 단어가 다르기 때문에 공통화 클래스로 제공을 할 수가 없음
- findByName(),findByEmail()처럼 메서드 이름 만으로 조회 하는 기능 제공
- 그 외에 findByNameAndId 등등..
- 페이징 기능 자동 제공
참고
- 실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용
- 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용
- Querydsl을 사용하면 쿼리도 자바코드로 안전하게 작성가능하고 동적 쿼리도 편리하게 작성 가능
- 그 외에도 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나 JdbcTemplate를 사용하면 됨
AOP가 필요한 상황
- 모든 메소드의 호출시간을 측정하거나 회원 가입 시간, 회원 조회 시간등을 측정하고 싶다면?
- 각 메서드마다 아래처럼 코딩을 해줘야 함
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");
}
}
...
public Long join(Member member) {
long start = System.currentTimeMillis();
try {
// 같은 이름이 있는 중복 회원 X
// ifPresent() (Optional에서 사용할 수 있는 메서드) : 값이 있으면 아래의 로직을 수행
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("join = " + timeMs + "ms");
}
}
...
문제
- 회원가입, 회원조회에 시간을 측정하는 기능은 핵심 관심 사항이 아님 -> 공통 관심 사항임
- 시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어려움
- 시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어려움
- 시간을 측정하는 로직을 변경할 떄 모든 로직을 찾아가면서 변경해야함
AOP적용
- AOP(Aspect Oriented Programming)
- 관점 지향 프로그래밍이라고도 불림
- 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 을 분리
- 공통 관심 사항을 분리하여 내가 원하는 곳에 적용
- 시간 측정 AOP등록
- 새로운 패키지에 TimeTraceAop class 작성
@Aspect
// 스프링 빈에 등록해야 함, Component를 쓰기도 하지만 직접 등록해서 인지할 수 있도록 하는 것이 좋음
@Component
public class TimeTraceAop {
// 적용할 타켓팅을 선정 할 수 있음 ("execution( 패키지명.패키지명.클래스명*(.파라미터타입))")
// 패키지 하위에 모두 적용 - @Component 애노테이션 사용시
@Around("execution(* start.startspring..*(..))")
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");
}
}
}
- 만약 직접 Bean을 등록하려면 SpringConfig 클래스를 수정(빈을 등록한 클래스)
// aop 스프링 빈을 직접 등록
@Configuration
public class SpringConfig {
...
@Bean
public TimeTraceAop timeTraceAop() {
return new TimeTraceAop();
}
}
-------------------------------------------------------------------------------
@Aspect
public class TimeTraceAop {
// 직접 @Bean을 등록 했을 경우 빈을 등록한 클래스를 AOP대상에서 제외해주면 됨
@Around("start.startspring..*(..))) && !target(start.startspring.SpringConfig)")
....
}
해결
- 회원가입, 회원 조회 등의 핵심 관심사항과 시간을 측정하는 공통 관심사항을 분리
- 시간을 측정하는 로직을 별도의 공통 로직으로 생성 / 핵심 관심 사항을 깔끔하게 유지
- 변경이 필요할 경우 공통 로직만 변경하면 됨
- 원하는 적용 대상을 선택 할 수 있음
스프링의 AOP 동작 방식
AOP 적용 전 / 후 의존관계
- 컨트롤러가 호출하면 프록시(가짜)에 스프링빈을 두었다가 joinPoint.Proceed() 메서드를 호출 하면 실제 스프링 빈에 적용
AOP 적용 전 / 후 전체 그림
- controller에서 생성자가 인젝션 할 때 .getClass()를 찍어보면 알 수 있음
출처 : 인프런 - 스프링 입문(무료) / 김영한님
'인프런 - 스프링 완전정복 코스 로드맵 > 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술(무료)' 카테고리의 다른 글
스프링 DB 접근 기술 1 - H2데이터베이스 설치, 순수JDBC, 스프링 통합 테스트 (0) | 2024.01.24 |
---|---|
스프링 빈과 의존관계 - 컴포넌트스캔과 자동 의존관계 설정, 자바 코드로 직접 스프링 빈 등록 / 회원 관리 예제 - 웹 MVC개발(홈 화면 추가, 회원등록, 회원조회) (1) | 2024.01.24 |
회원관리 예제 - 비즈니스 요구사항 정리, 회원 도메인과 리포지토리 만들기, 테스트 케이스 작성, 회원 서비스 개발, 서비스 테스트 (1) | 2024.01.23 |
스프링 웹 개발 기초 - 정적 콘텐츠, MVC와 템플릿 엔진, API (2) | 2024.01.23 |
프로젝트 환경설정 - 프로젝트생성, 라이브러리 탐색, View환경설정, 빌드 및 실행 (1) | 2024.01.22 |