반응형
반응형

 

 

"김영한 스프링 핵심 원리 기본편" 내용을 기반으로 작성한 내용입니다.

 

스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런

김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보

www.inflearn.com

 

📝Spring으로 전환하기

@Configuration
public class AppConfig {
  @Bean
  public MemberService memberService() {
    return new MemberServiceImpl(memberRepository());
  }

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

}
  • @Configuration
    • 해당 설정을 Spring Container로 사용하겠다는 의미이다.
  • @Bean
    • Spring Container에 등록하겠다는 의미로 해당 정보를 다른 곳에서 꺼내서 쓸 수 있게 도와준다. 기본적으로 메서드의 명을 스프링 빈의 이름으로 사용한다. (물론 변경 가능)
    • 주의점으로 Bean이름이 겹치면 안 된다. (유일해야 함)

 

public class MemberApp {
  public static void main(String[] args) {

    /** AppConfig설정이 들어간 Spring Container를 사용하겠다 **/
    ApplicationContext applicationContext = new
        AnnotationConfigApplicationContext(AppConfig.class);

    /** Spring Container에 등록된 Bean에서 MemberService를 가져오겠다. **/
    MemberService memberService =
        applicationContext.getBean("memberService", MemberService.class);


    /** 회원가입 **/
    Member member = new Member(1L, "memberA", Grade.VIP);
    memberService.join(member);

    /** 회원찾기 **/
    Member findMember = memberService.findMember(1L);

    System.out.println("new member = " + member.getName());
    System.out.println("find Member = " + findMember.getName());
  }
}

 

📝Spring Bean 조회해보기

부모타입으로 조회하면 자식 타입도 함께 조회한다. 그래서 Object 타입으로 조회하면 모든 스프링 빈을 조회하게 된다.

 

public class ApplicationContextInfoTest {

  AnnotationConfigApplicationContext ac = new
      AnnotationConfigApplicationContext(AppConfig.class);

  @Test
  @DisplayName("모든 빈 출력하기")
  void findAllBean() {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
      Object bean = ac.getBean(beanDefinitionName);
      System.out.println("name=" + beanDefinitionName + " object=" +
          bean);
    }
    // name=org.springframework.context.annotation.internalCommonAnnotationProcessor object=org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@602e0143
    // name=org.springframework.context.event.internalEventListenerProcessor object=org.springframework.context.event.EventListenerMethodProcessor@2c07545f
    // name=org.springframework.context.event.internalEventListenerFactory object=org.springframework.context.event.DefaultEventListenerFactory@e57b96d
    // name=appConfig object=com.spring.core.AppConfig$$SpringCGLIB$$0@32c726ee
    // name=memberService object=com.spring.core.member.MemberServiceImpl@22f31dec
    // name=memberRepository object=com.spring.core.member.MemoryMemberRepository@34c01041
  }

  @Test
  @DisplayName("애플리케이션 빈 출력하기")
  void findApplicationBean() {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
      BeanDefinition beanDefinition =
          ac.getBeanDefinition(beanDefinitionName);
      if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
        Object bean = ac.getBean(beanDefinitionName);
        System.out.println("name=" + beanDefinitionName + " object=" +
            bean);
        // name=appConfig object=com.spring.core.AppConfig$$SpringCGLIB$$0@38234a38
        // name=memberService object=com.spring.core.member.MemberServiceImpl@63fbfaeb
        // name=memberRepository object=com.spring.core.member.MemoryMemberRepository@602e0143
      }
    }
  }

  @Test
  @DisplayName("빈 이름으로 조회")
  void findBeanByName() {
    MemberService memberService = ac.getBean("memberService",
        MemberService.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
  }

  @Test
  @DisplayName("이름 없이 타입만으로 조회")
  void findBeanByType() {
    MemberService memberService = ac.getBean(MemberService.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
  }

  @Test
  @DisplayName("구체 타입으로 조회")
  void findBeanByName2() {
    MemberServiceImpl memberService = ac.getBean("memberService",
        MemberServiceImpl.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
  }
  
  @Test
  @DisplayName("특정 타입을 모두 조회하기")
  void findAllBeanByType() {
    Map<String, MemberRepository> beansOfType =
        ac.getBeansOfType(MemberRepository.class);
    for (String key : beansOfType.keySet()) {
      System.out.println("key = " + key + " value = " +
          beansOfType.get(key));
    }
    System.out.println("beansOfType = " + beansOfType);
    assertThat(beansOfType.size()).isEqualTo(2);
  }
  
  @Test
  @DisplayName("부모 타입으로 모두 조회하기 - Object")
  void findAllBeanByObjectType() {
    Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
    for (String key : beansOfType.keySet()) {
      System.out.println("key = " + key + " value=" +
          beansOfType.get(key));
    }
  }
}

정상 등록되어있는지 코드로 확인할 수 있다.

 

📝Spring Bean 컨테이너 등록 과정

김영한 스프링 핵심 원리 기본편
김영한 스프링 핵심 원리 기본편
김영한 스프링 핵심 원리 기본편
김영한 스프링 핵심 원리 기본편

 

반응형
반응형

 

"김영한 스프링 핵심 원리 기본편" 내용을 기반으로 작성한 내용입니다.

 

스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런

김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보

www.inflearn.com

 

📝스프링의 핵심 기능 사용하기 전

MemberServiceImpl

public class MemberServiceImpl implements MemberService {
  private final MemberRepository memberRepository = new
      MemoryMemberRepository();
      
  public void join(Member member) {
    memberRepository.save(member);
  }
  
  public Member findMember(Long memberId) {
    return memberRepository.findById(memberId);
  }
}

 

 

MemberService

public interface MemberService {
  void join(Member member);
  Member findMember(Long memberId);
}

위 코드는 회원가입하고 회원을 찾는 로직들이 들어간 코드들이다. MemberServiceImple에서 new MemoryMemberRepository()를 통해 Repository를 어떤 걸 쓸지 직접 정해주고 있어서 OCP/DIP를 위반하고 있다.

 

 

📝스프링의 핵심 기능 사용하기 후

AppConfig

public class AppConfig {
  public MemberService memberService() {
    return new MemberServiceImpl(new MemoryMemberRepository());
  }
}

 

 

MembmerServiceImpl

public class MemberServiceImpl implements MemberService {
  private final MemberRepository memberRepository;
  
  public MemberServiceImpl(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
  }
  
  public void join(Member member) {
    memberRepository.save(member);
  }
  
  public Member findMember(Long memberId) {
    return memberRepository.findById(memberId);
  }
}

 

MembmerService

public interface MemberService {
  void join(Member member);
  Member findMember(Long memberId);
}

AppConfig에서 New해서 관리하기 때문에 더이상 MemberServiceImpl에서 MemberRepository를 New로 넣을 필요 없고 코드 수정도 필요가없다. 이렇게 함으로 OCP/DIP를 MemberServiceImpl은 지킬 수 있고 그 책임을 AppConfig에서 하게 된다.

 

 

📝IoC / DI 컨테이너

프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)라고 한다. 위에 코드로 이야기하자면 AppConfig에서 모두 관리하기 때문에 이걸 IoC 컨테이너 또는 DI(의존 주입) 컨테이너라고 한다.

 

위 예시로 설명하면 MemberService에 대한 Repository 제어를 MemberService에서 하는 게 아니라 AppConfig에서 하기 때문에 제어가 역전되었다라고 이야기하고 DI 즉, 의존을 주입하는 역할을 해서 DI 컨테이너라고 불리는 것이다.

 

 

반응형
반응형

 

"김영한 스프링 핵심 원리 기본편" 내용을 기반으로 작성한 내용입니다.

 

스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런

김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보

www.inflearn.com

 

 

 

📝스프링 등장배경

옛날에는 EJB라는 프레임워크가 있었지만 너무 사용하기 어렵고 비용도 많이 들고 느리기 때문POJO(Plain한 상태)로 생각하자 해서 나온 게 Spring의 근간이 되는 부분이다. 그리고 EJB에서 사용했던 Entity Bean의 경우는 Hibernate가 대체하게 되었고 표준안이 된 게 JPA이다.

 

 

스프링 변경 순서

  • EJB Entity Bean → Hibernate → JPA
  • EJB → Spring

 

📝Spring Framework

Spring Framework는 스프링에 들어가는 핵심기술이 다 들어간 프레임워크이다.

 

📝Spring Boot

Spring Boot는 Spring Framework를 쉽게 사용할 수 있게 도와준다.

  • 스프링 설정편해졌다.
  • 내장 서버를 지원해 따로 서버를 깔 필요가 없다. (예를 들면 톰캣)
  • 외부라이브러리 버전 호환성을 알아서 잘 처리해준다.
  • 모니터링도 지원한다.

 

📝 좋은 객체지향이란?

다형성

"다양한 형태를 가질 수 있다" 라는 의미로 예를 들면 마우스가 고장나면 다른 마우스를 꽂아도 똑같이 동작하는 것처럼 USB포트의 설계만 동일하면 변경에 유연하고 용이하다.

 

객체 설계시에는 역할과 구현을 명확히 분리해야한다. 자바로 따지면 역할은 인터페이스이고 구현은 인터페이스를 이용한 클래스를 의미한다. 즉, 클라이언트는 인터페이스만 알면 된다. (엑셀을 밟으면 움직인다 / 브레이크를 밟으면 멈춘다)

 

인터페이스를 잘 설계했으면 클라이언트에서 따로 변경하지 않더라도 인터페이스를 이용한 클래스만 바꾸면 된다. 그래서 인터페이스 변화가 없게 설계하는게 정말 중요하다

 

 

 

📝 SOLID원칙

클린코드로 유명한 로버트 마틴이라는 사람이 좋은 객체 지향 설계를 위해 5가지 원칙을 정리한 것이다.

 

SRP (단일 책임 원칙)

하나의 클래스는 하나의 책임만 가져야한다. 즉, 변경이 있을 때 파급 효과가 적으면 해당 원칙을 잘 따른 것이다. 예를 들면 UI 변경시 모든 코드를 다 바꿔야하면 해당 원칙을 잘못 활용한 것이다.

 

OCP (개방 폐쇄 원칙)

확장에는 열려있지만 변경에는 닫혀있어야한다.

 

// 기존 코드
public class MemberService {
 private MemberRepository memberRepository = new MemoryMemberRepository();
}

// 변경 코드
public class MemberService {
// private MemberRepository memberRepository = new MemoryMemberRepository();
 private MemberRepository memberRepository = new JdbcMemberRepository();

}

위 코드를 보면 직접 코드를 변경해야하는 문제점이 있다. 즉, 다형성을 사용했지만 직접 주입 클래스를 바꿔야하기 때문에 OCP 원칙을 지킬 수 없다. 이럴 경우 객체를 생성하고 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다

 

 

LSP (리스코프 치환 원칙)

다형성에서 하위 클래스는 인터페이스 규약을 지켜야한다. 예를 들면 엑셀을 밟으면 앞으로 나가는 게 일반적이지만 뒤로 가게도 만들 수 있는데 뒤로 가게끔 만들면 해당 원칙을 위반한 것이다.

 

ISP 인터페이스 분리 원칙

범용 인터페이스 하나보단 여러개의 인터페이스가 좋다. 예를 들면 자동차 인터페이스를 운전, 정비 인터페이스를 분리시키면 정비 인터페이스가 바뀌더라도 운전 인터페이스에 영향을 끼치지 않는다.

 

DIP 의존관계 역전 원칙

프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다. 즉, 인터페이스에 의존해야지 클래스에 의존하면 안 된다. 예를 들면 로미오라는 배역이라는 역할 자체가 바뀌면 안 되지 로미오를 연기하는 사람은 바뀌어도 괜찮다.

 

📝 SOLID 문제점

다형성 만으로는 OCP와 DIP 즉, 직접 코드를 프로그래머가 바꿔줘야하는 문제점을 해결할 수 없다. 이걸 해결하기 위해 스프링이 나오게 되었다.

 

 

 

 

 

반응형
반응형

 

📝전체 URL 적용

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("*") // 모든 origin 허용 (보안상 주의)
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("*");
    }
}

 

  • addMapping
    • 내가 작성한 어떤 라우팅에 대해서 CORS를 허용할 지
  • allowedOrigins
    • 어떤 요청자에 대해서 허용시킬 것인지
  • allowedMethods
    • 어떤 HTTP 메소드에 대해서 허용할지
  • allowedHeaders
    • 어떤 헤더 값에 대해서 허용할지

 

📝부분 라우팅 적용

@CrossOrigin(origins = "*")
@GetMapping("/next")
public @ResponseBody HashMap<String, Object> next() throws InterruptedException {

	System.out.println("next2 call!!");
	HashMap<String, Object> map = new HashMap<String, Object>();
	map.put("id", "Next 테스트");
	
	Thread.sleep(3000);
	
	return map;
}
반응형
반응형

 

📝@Rollback

@Transactional
@Rollback(value = false)
class MemberRepositoryTest {
    ...
}

@Transactional에서 장애 발생시 롤백 시키는 걸 할지 말지에 대한 것이다. 일반적으로 사용하지 않으며 테스트 코드 작성해서 디비 데이터 값을 확인해야한다면 사용한다 (테스트 코드 작동한 후 종료된 뒤에 바로 롤백 시켜서 디비에서 확인하기가 어려움)

 

📝@PostConstruct

// 초기 데이터 INSERT
@PostConstruct
public void insertData() throws Exception {

    IntStream.range(0, 100).forEach((i) -> {
        Member member = new Member("username" + i);
        memberRepository.save(member);
    });
}

종속성 주입이 완료된 후 실행되어야 하는 메서드에 사용한다 여기에서는 정상으로 스프링부트가 기동 된다면 해당 메소드가 동작해 초기 데이터를 INSERT하는 작업이다.

 

📝@CrossOrign

@CrossOrigin(origins = "*")
@GetMapping("/next")
public @ResponseBody HashMap<String, Object> next() throws InterruptedException {

	System.out.println("next2 call!!");
	HashMap<String, Object> map = new HashMap<String, Object>();
	map.put("id", "Next 테스트");
	
	Thread.sleep(3000);
	
	return map;
}

어떤 라우팅에 대해서 URL 요청을 허용할 지

반응형
반응형

📝@EnableJpaAuditing, @EntityListeneres, @CreateBy, @LastModifiedBy, @CreateDate, @LastModifiedDate

@EnableJpaAuditing // 필수
@SpringBootApplication
public class DataJpaApplication {

	public static void main(String[] args) {
		SpringApplication.run(DataJpaApplication.class, args);
	}

	@Bean
	public AuditorAware<String> auditorProvider() {
		return () -> Optional.of(UUID.randomUUID().toString());
		// 실무에서는 세션 정보나, 스프링 시큐리티 로그인 정보에서 ID를 받음
	}

}

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;

}

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseTimeEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;

}


@Entity
public class Member extends BaseEntity {

    @Id
    @GeneratedValue
    private String id;
    
    private String username;

}

 

  • @EnableJpaAuditing
    • 아래 어노테이션을 쓰기 위해 필수 기능이다
  • @EntityListeneres
    • 해당 엔터티는 JPA Entity에 이벤트 발생시 콜백 처리하라는 의미이다.
  • @CreateBy
    • 생성자
  • @LastModifiedBy
    • 수정자
  • @CreateDate
    • 등록일자
  • @LastModifiedDate
    • 수정일자

 

📝@PersistenceContext

@PersistenceContext
EntityManager em;

스프링 부트에서 생성한 EntityManager를 DI시켜준다

반응형
반응형

📝@NamedQuery, @Query

@Entity
@NamedQuery(
    name="Member.findByUsername", // Key
    query="select m from Member m where m.username = :username" // Value
)
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;
    private int age;

}

public interface MemberRepository extends JpaRepository<Member, Long> {

    /** ----- 방법1 (@Query) ----- **/
    @Query(name ="Member.findByUsername")
    List<Member> findByUsername(@Param("username") String username);
    
    /** ----- 기본 예제 ----- **/
    @Query("select m from Member m where m.username = :username and m.age = :age")
    List<Member> findUser(@Param("username") String username, @Param("age") int age);

}

조건절 조회하는 임의의 쿼리가 필요할 때 Entity에 @NamedQuery를 이용해 JpaRepository에서 {키:값} 형태로 찾아 사용이 가능하다

이 방법보단 JpaRepository에서 바로 @Query를 이용해 바로 작성하는 걸 더 많이 사용한다

 

📝@Modifying

public interface MemberRepository extends JpaRepository<Member, Long> {

    @Modifying(clearAutomatically = true)
    @Query("update Member m set m.age = m.age + 1 where m.age >= :age")
    int bulkAgePlus(@Param("age") int age);
 
}

Update한 후에 Update 항목을 조회하는 경우가 있을 때 영속성 컨텍스트 캐시를 날리고 데이터 조회하기 때문에 디비에서 조회하게 되고  데이터의 정합성을 맞춘다

 

📝@EntityGraph

public interface MemberRepository extends JpaRepository<Member, Long> {

    @EntityGraph(attributePaths = {"team"})
    @Query("select m from Member m")
    // 위에 코드는 EntityGraph + Query로 아래와 같은 코드이다. (team Fetch Join)

    // @Query("select m from Member m left join fetch m.team")
    List<Member> findMemberEntityGraph();
}

패치조인을 더 쉽게할 수 있도록 스프링 데이터 JPA에서 제공해준다.

 

📝엔터티 라이프 사이클 컨트롤 어노테이션

@PrePersist : 새로운 엔티티에 대해 persist가 호출되기 전
@PreUpdate : 엔티티 업데이트 작업 전
@PreRemove : 엔티티가 제거되기 전
@PostPersist : 새로운 엔티티에 대해 persist가 호출된 후
@PostUpdate : 엔티티가 업데이트된 후
@PostRemove : 엔티티가 삭제된 후
@PostLoad : Select조회가 일어난 직후에 실행

 

 

 

반응형
반응형

📝@Entity, @Id, @GeneratedValue, @Temporal, @Lob

public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // IDENTITY 전략
    // @GeneratedValue(strategy = GenerationType.SEQUENCE) // SEQUENCE 전략
    @Column(name="member_id")
    private Long id;

    private String name;
    private int age;
    private String address; // 필드만 추가해주면 끝

    // @Enumerated(EnumType.ORDINAL) // enum 타입 매핑 [숫자] (금지)
    @Enumerated(EnumType.STRING) // enum 타입 매핑 [문자] (사용 OK)
    private CategoryEnum name;

    // 날짜 타입 매핑 [아래 LocalDateTime을 쓰면 알아서 YYYYMMDDHHMMSS 설정]
    @Temporal(TemporalType.DATE) 
    private Date date;
    
    private LocalDateTime createDate
    
    @Lob // BLOB, CLOB 매핑
    private String description;
}

 

  • @Entity 
    • JPA에서 영속성 컨텍스트가 관리할 테이블이라는 의미이다.
  • @Id
    • 해당 필드를 PK로 지정하겠다는 의미이다.
  • @GeneratedValue
    • PK AutoIncrement설정이다. IDENTITY로 설정할지 SEQUENCE로 지정할지 정할 수 있다.
  • @Temporal
    • 날짜 필드라는 걸 알리는 의미이다.
  • @Lob
    • BLOB, CLOB타입이라는 걸 알리는 의미이다.

 

📝@JoinColumn, @ManyToOne, @OneToMany, @OneToOne, @ManyToMany

@Entity
public class Member {

    @Id // PK로 설정할 컬럼
    @GeneratedValue
    private long id;

    @Column(nullable = false, length = 10)
    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID") // Team 테이블[클래스]에 연결할 필드명을 알려주세요 
                                  // (JoinColumn은 FK가 있는 테이블에 들어간다)
    private Team team;

}

@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;

}
  • @JoinColumn
    • 연결할 테이블에 필드를 지정한다
  • @ManyToOne
    • 다대일 관계로 연결하겠다는 의미
  • @OneToMany
    • 일대다 관계로 연결하겠다는 의미
  • @OneToOne
    • 일대일 관계로 연결하겠다는 의미
  • @ManyToMany (사용 X)
    • 다대다 관계로 연결하겠다는 의미 (다대다 관계는 무조건 다대일로 풀어내야한다)

 

 

📝@Inheritance, @DiscriminatorColumn, @DiscriminatorValue

@Inheritance(strategy = InheritanceType.JOINED) // 조인 전략
@DiscriminatorColumn 
@Entity
public class Item {

    @Id
    @GeneratedValue
    private long id;

    private String name;
    private int price;
}

@Entity
// @DiscriminatorValue("this is movie")
public class Movie extends Item{

    private String director;
    private String actor;

}

public class Main {
    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        tx.begin();

        try{

            Movie movie = new Movie();
            movie.setName("바람과 함께 사라지다");
            movie.setPrice(10000);
            movie.setActor("이민호");
            movie.setDirector("봉준호");

            em.persist(movie);

            tx.commit();

        }catch (Exception e){
            e.printStackTrace();
        }finally{
            emf.close();
            em.close();
        }
    }
}

 

@Inheritance

관계형 데이터베이스에 상속관계라는 걸 JPA에서 표현했다

 

 

 

 

@DiscriminatorColumn

해당 어노테이션을 사용 안 하면 DTYPE이 따로 안 보이는데 이걸 명시적으로 표현해주는 어노테이션 어느 자식이 UPSERT 되어서 그런지 잘 모르니 해당 어노테이션을 사용하는게 좋다

 

 

 

 

@DiscriminatorValue

해당 어노테이션을 사용하면 DTYPE에 들어갈 걸 명시적으로 지정할 수 있다.

 

 

 

 

 

 

 

 

📝@MappedSuperclass

@MappedSuperclass
public abstract class BaseEntity {

    @Column(name = "create_date") // 부모 클래스에서 컨트롤 가능
    private LocalDateTime localDateTime;

    private LocalDateTime modi_date;
}

@Entity
public class Book extends BaseEntity{

    @Id
    @GeneratedValue
    private long id;

    private String name;
    private String author;
    private String ISBN;

}

Inheritance와 차이가 있는 점은 Inheritance은 부모 자식 테이블 따로따로 들어가게 되는데 MappedSuperclass의 경우 필드만 공유하고 하나의 테이블로 만들어진다.

 

 

📝@NamedQuery, @Query

@Entity
@NamedQuery(
    name="Member.findByUsername", // Key
    query="select m from Member m where m.username = :username" // Value
)
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;
    private int age;

}

public interface MemberRepository extends JpaRepository<Member, Long> {

    /** ----- 방법1 (@Query) ----- **/
    @Query(name ="Member.findByUsername")
    List<Member> findByUsername(@Param("username") String username);
    
    /** ----- 기본 예제 ----- **/
    @Query("select m from Member m where m.username = :username and m.age = :age")
    List<Member> findUser(@Param("username") String username, @Param("age") int age);

}

조건절 조회하는 임의의 쿼리가 필요할 때 Entity에 @NamedQuery를 이용해 JpaRepository에서 {키:값} 형태로 찾아 사용이 가능하다

이 방법보단 JpaRepository에서 바로 @Query를 이용해 바로 작성하는 걸 더 많이 사용한다

 

📝@Modifying

public interface MemberRepository extends JpaRepository<Member, Long> {

    @Modifying(clearAutomatically = true)
    @Query("update Member m set m.age = m.age + 1 where m.age >= :age")
    int bulkAgePlus(@Param("age") int age);
 
}

Update한 후에 Update 항목을 조회하는 경우가 있을 때 영속성 컨텍스트 캐시를 날리고 데이터 조회하기 때문에 디비에서 조회하게 되고  데이터의 정합성을 맞춘다

 

📝@EntityGraph

public interface MemberRepository extends JpaRepository<Member, Long> {

    @EntityGraph(attributePaths = {"team"})
    @Query("select m from Member m")
    // 위에 코드는 EntityGraph + Query로 아래와 같은 코드이다. (team Fetch Join)

    // @Query("select m from Member m left join fetch m.team")
    List<Member> findMemberEntityGraph();
}

패치조인을 더 쉽게할 수 있도록 스프링 데이터 JPA에서 제공해준다.

 

엔터티 라이프 사이클 컨트롤 어노테이션 (@PrePersist, @PreUpdate, @PreRemove, @PostPersist, @PostUpdate, @PostRemove, @PostLoad)

@PrePersist : 새로운 엔티티에 대해 persist가 호출되기 전
@PreUpdate : 엔티티 업데이트 작업 전
@PreRemove : 엔티티가 제거되기 전
@PostPersist : 새로운 엔티티에 대해 persist가 호출된 후
@PostUpdate : 엔티티가 업데이트된 후
@PostRemove : 엔티티가 삭제된 후
@PostLoad : Select조회가 일어난 직후에 실행
반응형
반응형

📝 @Getter, @Setter, @RequriedArgsConstructor, @Data, @AllArgsConstruct, @NoArgsConstructor (롬복 필요)

@Getter // Getter 생성
@Setter // Setter 생성 
@RequiredArgsConstructor // 생성자 DI
@Data // Getter + Setter 생성
@AllArgsConstructor // 생성자 생성
@NoArgsConstructor // 기본생성자
@ToString(of = {"id","password"})
public class Account {

	// private final String id; // DB에서 값을 받아와 수정할 일이 없을 때는 final 로 쓰는게 trend
	// private final int password; // DB에서 값을 받아와 수정할 일이 없을 때는 final 로 쓰는게 trend 
	private String id;
	private int password;
	
	@Builder // 내가 원하는 생성자 파라미터 지정 가능
	public Account(String id, int password) {
		this.id = id;
		this.password = password;
	}
	
}

 

 

📝 @ResponseStatus

@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public String responseBodyJsonV2() {

    String logInfo = "abc";
    log.info("log : {}", logInfo);

    return logInfo;
}

HTTP 통신 상태 코드값을 지정할 수 있습니다.

 

📝 @ExceptionHandler

public class ApiExceptionV2Controller {

	...
    @ResponseStatus(HttpStatus.BAD_REQUEST) // 해당 Status 설정 없으면 200(정상)이 나가기 때문에 따로 설정 필요
    @ExceptionHandler(IllegalArgumentException.class) // 해당 컨트롤러에서만 IllegalArgumentException 발생 시 작동
    public ErrorResult illegalExHandler(IllegalArgumentException e){
        log.error("[exception] ex",e);
        return new ErrorResult("BAD",e.getMessage());
        /**
         * {
         *   "code": "BAD",
         *   "message": "잘못된 입력 값"
         * }
         */
    }
    
    @GetMapping("/api2/members/{id}")
    public MemberDto getMember(@PathVariable("id") String id) {

        if (id.equals("ex")) {
            throw new RuntimeException("잘못된 사용자");
        }
        if (id.equals("bad")) {
            throw new IllegalArgumentException("잘못된 입력 값");
        }
        if (id.equals("user-ex")) {
            throw new UserException("사용자 오류");
        }

        return new MemberDto(id, "hello " + id);
    }
}

 

 

현 컨트롤러 내에서 지정한 에러 발생시 핸들링할 수 있는 역할을 한다

 

📝 @Transactional

@Transactional
public void updateSomething(MembmerDto membmerDto){

    // ... 업데이트
    // 실패!! -> RollBack

}

 

📝 @TestConfiguration

@TestConfiguration
static class TestConfig {

    private final DataSource dataSource;

    public TestConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    MemberRepositoryV3 memberRepositoryV3() {
        return new MemberRepositoryV3(dataSource);
    }

    @Bean
    MemberServiceV3_3 memberServiceV3_3() {
        return new MemberServiceV3_3(memberRepositoryV3());
    }

}

테스트 환경에서 @Configuration역할을 한다.

반응형