코드도 줄고, 이제 쿼리도 알아서 날림.
객체 중심으로 설계, 생각 가능.
build.gradle에
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
얘 추가.
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
이것까지 포함이라 이거는 주석처리 해도 됨.
그 다음 application.properties
spring.jpa.show-sql=true
이렇게 하면 jpa가 날리는 sql 볼 수 있음.
spring.jpa.hibernate.ddl-auto=none
jpa는 객체를 보고 테이블을 알아서 만들어 주는데, 우리는 이미 만들어 줬으니 저거 none 해두면 안만듦.
ORM Object Relational Mapping
오브젝트와 관계형 데이터를 맵핑
@Entity //이 객체는 JPA로 관리를 하겠다.
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) //Id이고, 생성되어지는 값, DB가 직접 생산해주는 IDENTITY 값임.
private Long id;
// @Column(name="username") 만약 db의 이름과 다르다면 앞처럼 하면 됨.
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
public class JpaMemberRepository implements MemberRepository{
private final EntityManager em; //jpa는 이걸로 모든 걸 동작.
public JpaMemberRepository(EntityManager em) { //build.gradle에 implementation해두면 spring이 자동으로 이걸 생성 해줌. 그래서 이렇게 생성자로 주입 받아야 함. 스프링에서 자동으로 해줄듯?
this.em = em;
}
@Override
public Member save(Member member) {
em.persist(member); //저장 끝. db에다 insert 쿼리 날리고 setID까지 (아마 Member 클래스에 jpa 설정해 뒀으니 알아서 setId 찾아서 해주는 듯?) 다 해줌.
return member;
}
@Override
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id); //Member 클래스를 조회하고 뭘로 찾을 건지 (id) 알려주면 됨. id같은 경우는 필수값이라 이렇게 조회 가능.
return Optional.ofNullable(member);
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class) //쿼리를 만드는 데, :name 이거는 플레이스 홀더임.
.setParameter("name",name) //저 플레이서 홀더에("name" 왜냐하면 쿼리문도 ""니까), String name을 넣음. 바인딩 해주는 거임.
.getResultList(); //이때가 조회하는 타이밍임. createQuery는 말 그대로 쿼리만 만듦. 이 때가 쿼리 보내서 실행하고 값 받는 순간.
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return em.createQuery("select m from Member as m", Member.class) //일반적인 쿼리와는 조금 다른, 객체지향 쿼리. 객체지향으로 쿼리를 날림. sql로 번역 됨.
.getResultList(); //Member Entity 상대로 조회해, 그걸 as m, m이라 별칭하고, select m, 그 m을 가져왔네.
} //객체지향적 쿼리라 id, name 둘다 찾을 필요 없이 이미 맵핑이 다 되어있음.
}
여기서 저 findByName 쿼리문에 빨간 줄 떠서 뭔가 싶었는데 걍 됨. " " 안이라서 그런지 얘가 잘못 잡았나..?
참고로 DB 쓸때는 꼭 DB상태를 변화시키는 곳에는
@Transactional
해줘야 함.
@Transactional
public class MemberService { //Ctrl + Shift + T 테스트 바로 만들기.
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
//회원가입
public Long join(Member member){ //id는 임시로 반환하게 한 듯.
//같은 이름일 시 중복회원 X
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName()) //여기까지 Optional로 반환
.ifPresent(member1 -> { // ifPresent는 값이 있으면, 그러니까 참이라면 아래 코드를 실행
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId){
return memberRepository.findById(memberId);
}
}
기존 MemberService에
@Transactional
만 붙임.
트랜잭션은 DB 상태를 변화 시키기 위해서 수행하는 작업이란 뜻 이므로, save엔 씀.
@Configuration
public class SpringConfig {
//private DataSource dataSource;
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 라이브러리 설치한다고 build.gradle에 명시하면 스프링이 알아서 EntityManager 만들어 줌.
그래서 @Autowired 하면 알아서 들어감.
@Transactional //이거는 @Test 함수 끝낸 후 날린 DB 쿼리를 롤백
class MemberServiceIntegrationTest {
@Autowired
MemberService memberService; //내가 테스트 하는 것 이므로 테스트는 편한 방법으로.
@Autowired
MemberRepository memberRepository;
@Test
@Commit
void 회원가입() { //테스트때는 한국인 기업이면 한글로도 쓴다고 함.
//given 주어진 상황
Member member = new Member();
member.setName("hello2");
//when 실행했을 때, 검증
Long saveId = memberService.join(member); //동일 이름이 있을 시 예외 던짐. 아니면 제대로 memberRepository에 save 되고 id 반환
//then 결과, 검증 보통 거의 다 이렇게 됨. 모두 해당되는 건 아님. GWT
Member findedMember = memberService.findOne(saveId).get(); //.get()은 보통 테스트때에만 씀.
Assertions.assertThat(member.getName()).isEqualTo(findedMember.getName());
}
@Test
public void 중복_회원_예외(){
//given
Member member1 = new Member();
member1.setName("spring1");
Member member2 = new Member();
member2.setName("spring1");
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); //assertThrows는 junit 함수. 예외를 반환.
//뒤의 람다식을 실행 시, 앞쪽의 예외를 throws해야 성공. 아니면 멈춤.
Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// try{
// memberService.join(member2);
// fail(); //Junit의 에러 발생시키는 함수. 같은 이름이므로 예외 없이 정상적으로 작동하면 실패임.
// }catch (IllegalStateException e){
// Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// }
//then
}
}
테스트. 저번이랑 거진 똑같은 데 하나, @Commit을 붙였는데 이렇게 해 두면 원래 이거 테스트 하면 DB에 저장시키고, 롤백을 하기 위해 @Transactional 썼었는데,Commit하면 상관없이 롤백 안되고 DB에 저장됨.
'스프링 > 0. 입문, 전체방향' 카테고리의 다른 글
21강. AOP (0) | 2023.07.12 |
---|---|
20강. 스프링 JPA (0) | 2023.07.12 |
18강. Jdbc 템플릿 (0) | 2023.07.09 |
17강. spring 종합 테스트 (0) | 2023.07.09 |
16강. 순수 JDBC (0) | 2023.07.09 |