728x90

Spring Data JPA

JPA를 사용해도 마찬가지로 DB의 기본적인 CRUD는 어찌됐든 사용하기 마련이다.
근데 이것을 가지고 똑같이 계속 사용해서 엔티티마다 만들어 주는 개념이 아니라
인터페이스에 제네릭 타입을 사용해서 엔티티를 매칭해주어 구현을 하는것이 편리하다.

이 repository를 개발할 때 인터페이스만 정의주면 실행 시점에 Spring Data JPA가 구현 객체를 동적으로 생성해서 주입해준다.
그래서 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발이 가능 하다.

public class MemeberRepository extends JpaRepository<Member, Long> {
    List<Member> findAll();
}

이런식으로 구현을 해준다.
MemberRepository에는 @Repository 를 넣어주지 않는다.

왜냐면

JpaRepository를 상속받은 JpaRepositoryImplementation 를 구현한
SimpleJpaRepository

image
{: text-center}

그림과 같이 @Repository 를 가지고 있다.

그래서 인터페이스만 잘 작성해주면 된다.

제네릭에는 JpaRepository<엔티티, 식별자타입> 이렇게 만들어준다.

JpaRepository의 주요 메서드

  • save() : 새로운 엔티티는 저장하고 이미 있는 엔티티는 수정한다.
  • delete() : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 를 호출한다.
  • findOne() : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 를 호출한다.
  • getOne() : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 를 호출한다.
  • findAll() : 모든 엔티티를 조회한다. 정렬이나 페이징 조건을 파라미터로 전달할 수 있다.
  • findBy() : 엔티티 하나를 조회한다. Optional 타입의 객체를 반환한다.

반환 타입

Spring Data JPA는 만약 조회 결과가 없는 경우 컬렉션을 반환하는 메소드에서는 null을 반환한다.

단건으로 두 건 이상 겹쳐서 조회가 될 때 다음의 예제를 보자.

public interface MemberRepository extends JpaRepository<Member, Long> {
    Member findByName(String name);
}

@DataJpaTest
class MemberTest {
  @Test
  void 두건_조회_테스트() {
      Address address = Address.builder()
          .city("city")
          .street("street")
          .zipcode("zipcode")
          .build();

      Address comAddress = Address.builder()
          .city("cocity")
          .street("costreet")
          .zipcode("cozipcode")
          .build();

      Period period = Period.of("20210714", "20210714");

      Member member = Member.builder()
          .name("name")
          .age(26)
          .period(period)
          .homeAddress(address)
          .companyAddress(comAddress)
          .build();

      Member member2 = Member.builder()
          .name("name")
          .age(26)
          .period(period)
          .homeAddress(address)
          .companyAddress(comAddress)
          .build();

      memberRepository.save(member);
      memberRepository.save(member2);

      memberRepository.findByName("name");
  }
}

같은 삽입 코드를 두개 복사했다. 감안해서 보자.

일단 Member 엔티티인 member, member2를 저장하고 Name 컬럼의 값으로 하나를 가져오는 것인데, 복사해서 붙여넣었으니 같은 값이 즉 2개 이다.
하나의 데이터만 가져오지 못한다면,

image
{: text-center}

NonUniqueResultException 을 발생시킨다.

정확하게 1개 반환하는 것이 아니라면 List로 받는게 오히려 좋을 수도 있겠다.

findById()와 findAll()의 차이를 알고 싶다면

쿼리 메서드

Spring Data JPA가 제공하는 기능이다. 메소드 이름만으로 쿼리를 생성하는 기능이 있는데 인터페이스에 메소드만 선언해주면 해당 메소드 이름으로의 적절한 JPQL 쿼리를 생성해서 날려준다.

  • 메소드 이름으로 쿼리 생성
  • 메소드 이름으로 JPA NamedQuery 호출
  • @Query 사용하여 인터페이스에 직접 정의

쿼리 메소드의 규칙에 따라서 정의를 해주어야 동작한다.

쿼리 메소드 규칙

키워드 JQPL 예
And findByNameAndAddress where x.Name = ?1 and x.Address = ?2
Or findByNameOrAddress where x.Name = ?1 or x.Address = ?2
Distinct findDistinctByLastnameAndFirstname select distinct … where x.lastname = ?1 and x.firstname = ?2
Is、Equals findByFirstname, findByFirstnameIs, findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull、Null findByAge(Is)Null … where x.age is null
IsNotNull、NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 ( %가 뒤에 추가된 매개 변수)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 ( %가 앞에 추가된 매개 변수)
Containing findByFirstnameContaining … where x.firstname like ?1 ( %가 래핑된 매개 변수)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstname) = UPPER(?1)

NamedQuery

보통 @NamedQuery 로 정의하는 방식인데, 이거는 MyBatis를 쓰는 방식같은 느낌이 많이 든다.

이것은 전의 포스팅을 보면 이해가 쉬울 수 있다.

@Query, Repository 메소드에 쿼리 정의

이 방법이 더 간단하고 편리하게 정의할 수 있는 느낌이 더 강하다.

이름없는 NamedQuery 라고 생각하면 좋을듯? 싶다 😁

장점이라고 하면 애플리케이션 실행 시점에 문법 오류를 발견할 수 있어서 좋다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    @Query("select m from Memer m where m.name = ?1")
    Member findByName(String name);
}

사용은 이런식으로 하면 되겠다.

Native SQL을 사용하려면 @Query("sql", nativeQuery=true) 로 설정하면 되고 이 네이티브 쿼리에서는 파라미터 바인딩 숫자가 0부터 시작한다.

0부터 시작하면 뭐하나 인덱스 밀려서 쓰면 값 에러 날 수 있으니 권장하지 않는다.

위치 기준 파리미터 주의점

위치(순서) 기준보다는 이름 기준의 바인딩을 추천한다. 위치 기준의 바인딩을 사용하면 추후에 요소가 추가되면 밀리거나 땡겨질 수가 있기 때문❗️

그래서 아래와 같은 방법을 더 많이 쓴다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    @Query("select m from Memer m where m.name = :name")
    Member findByName(@Param("name") String name);
}

정렬

Spring Data JPA에는 쿼리 메소드에 페이징 그리고 정렬기능을 사용하도록 2가지 파라미터를 제공한다.

  • org.springframework.data.domain.Sort : 정렬 기능
  • org.springframework.data.domain.Pageable : 페이징 기능 (내부에 Sort) 포함

여기서 정렬 실습에 대한 에러도 같이 남긴다.

우선 sql DDL 그리고 DML 이다.

member.sql

CREATE TABLE IF NOT EXISTS MEMBER
(   ID              BIGINT GENERATED BY DEFAULT AS IDENTITY,
    NAME            VARCHAR(255),
    AGE             INTEGER NOT NULL,
    COMPANY_CITY    VARCHAR(255),
    COMPANY_STREET  VARCHAR(255),
    COMPANY_ZIPCODE VARCHAR(255),
    CITY            VARCHAR(255),
    STREET          VARCHAR(255),
    ZIPCODE         VARCHAR(255),
    END_DATE        VARCHAR(255),
    START_DATE      VARCHAR(255),
    PRIMARY KEY (ID)
);

INSERT INTO MEMBER (name, age, company_city, company_street, company_zipcode, city, street, zipcode, end_date, start_date) VALUES ('테스트', 33, 'city1', 'street', 'zipcode', 'cocity1', 'costreet', 'cozipcode', '20210714', '20210714');
같은 쿼리 여러개 넣었다고 생각하자.
...
...
...

처음에 이렇게 쿼리메소드를 구현하였다.🙂

public interface MemberRepository extends JpaRepository {
    //기존 로직
    ...

    Page<Member> findByName(String name, Pageable pageable);
}

그리고 테스트코드에서는...

Page<Member> members = memberRepository.findByName("테스트", PageRequest.of(0, 3, Sort.by(Order.desc("age"))));

이렇게 Page<T>를 사용하면 페이징 처리를 할 수있다.

첫 페이지는 0부터 시작한다.

이슈 발생

여기서 생긴 이슈는 분명 Sort.by(Order.desc("정렬할 컬럼")); 으로 정렬을 넣어줬음에도 쿼리에서는 정렬을 해주지 않았고 근데 정렬방식은 DESC로 나오는 경우였다.

findByName
{: text-center}

result
{: text-center}

이렇게 페이징을 위해 count쿼리는 나온다. 근데 where 이후로 order by 가 없는 것이다.

스터디를 하는 분과 같이 의논을 Code With Me로 같이 삽질을 좀 했다.

p.s 시간 내주셔서 감사합니다!

결과 알아낸 것은 내가 쿼리 메소드로 구현한건 findBy 로 시작하는 쿼리메소드를 오버로딩해서 Pageable만 추가해줬다.

이렇게 하니까 결국 기본으로 Spring Data JPA에서 정의된 쿼리메소드 참조를 하기 때문에 우선순위가 그쪽으로 배정되어서 조회는 ASC로 되는가보다.

해결책

public interface MemberRepository extend JpaRepository {
    Page<Member> findMembersByName (String name, Pageable pageable);
}

보다시피 메서드 이름을 findMembersByName 으로 바꿨고,

@DataJpaTest
@ActiveProfiles("test")
public class DbTest {

    @Autowired
    MemberRepository memberRepository;

    @Test
    @Sql("classpath:member.sql")
    void dbTest() throws Exception {
        Page<Member> members = memberRepository.findMembersByName("테스트", PageRequest.of(0, 3, Sort.by(Order.desc("age"))));


        Pageable pageable = members.getPageable();

        System.out.println("pageable = " + pageable);
        System.out.println("pageable.getSort() = " + pageable.getSort());
    }
}

이렇게 해주니까

findMembersByName
{: text-center}

잘 나오게 된다.

2021-08-22 수정본

위 부분은 사실 단순한 해결이었고, 진정한 해결책은 우선순위는 @NamedQuery 에 있다.

실습때문에 Member 엔티티에 NamedQuery를 추가했었어서 이 부분이 먼저 구현되어 비정상적으로 정렬이 됐던 것이다. 스터디원들 다 같이 모여 해결했다. 이럴때 집단지성이 굉장히 좋은것 같다👍

아무튼 구문자체에는 오류가 없었고 네임드 쿼리 쓰지말자! Mybatis를 보면 볼 수록 생각나게 한다. 이제 정을 떼는것이 좋을것 같다.

좀 더 편리하게 사용하려면 interface다중상속이 가능하기 때문에
이렇게 상속해서 사용해도 될 것 같다.

이상으로 12장 포스팅을 마치도록 하겠다.

728x90

'Spring' 카테고리의 다른 글

Jasypt  (0) 2022.08.07
Spring -> Spring Boot 마이그레이션 2  (0) 2022.08.06
Spring -> Spring Boot 마이그레이션  (0) 2022.08.05
[Spring] MockMvc Bean 주입 에러  (0) 2022.08.04
728x90

내 팀 프로젝트를 예전에 Spring으로 구현을 했었는데 이것을 스프링부트로 마이그레이션 해보았다. 더불어 Maven 의존성을 Gradle로 바꾸면서 마이그레이션을 한 것이다.

오류들이 상당히 많았지만 남들과 같은 오류인지는 잘 모르겠다.🤣

어떤 오류들이 있었는지 알아보자

Lombok

일단 Jar로 빌드하게되면 SpringBoot는 WEB-INF안의 jsp파일을 읽을수가 없다. 그래서 war로 빌드를 해주어야한다.

추후에 Mybatis로 db연결을 하는 방식을 JPA로 변경할 예정이다.

우선 제일 먼저 gradle 을 의존성 관리 툴로 추가하기 위해서

pom.xml 이 있는 경로로 가서 gradle init을 시켜주었다.

gradle init --type pom

이것으로 pom.xml의 내용이 Gradle로 변환이된다.

그다음 war파일이 있어야 실행할 수 있는 환경이 되기 때문에

apply plugin: 'war'

를 해준다.
이왕이면 최신버전으로 마이그레이션 하자 생각해서 다 최신버전으로 엮어주었다.

여기서 에러가 발생했던건 lombok이었는데
implementaion만으로 lombok을 추가하는 것이 아니라
annotationProcessor로도 lombok을 추가해줘야 했었다.

간략하게 gradle 지시어를 정리하자면

  • compileOnly: 해당 의존성을 컴파일시에만 포함한다.

  • runtimeOnly: 해당 의존성을 런타임시에만 포함한다.

  • compile: 해당 의존성을 직/간접적으로 의존하고 있는 모든 의존성을 재빌드한다.

  • implementation: 해당 의존성을 직접 의존하고 있는 의존성만 재빌드 한다.

기본적으로 포함이 되어있지 않은 어노테이션이 바로 lombok이기 때문에
annotationProcessor로 명시적 추가를 해줘야한다.

Mybatis

탈 xml을 하기 위해서 이것도 다 Java의 @Bean으로 설정을 해주었다.
여기서도 에러가 조금 많이 발생했다.

@Configuration
@MapperScan(
        sqlSessionFactoryRef="dataSource",
        sqlSessionTemplateRef="sqlSessionFactoryBean")
public class MapperConfig {
    @Value("${spring.datasource.driver-class-name}")
    String driverClassName;

    @Value("${spring.datasource.url}")
    String url;

    @Value("${spring.datasource.username}")
    String userName;

    @Value("${spring.datasource.password}")
    String password;

    @Bean(name="dataSource")
    public DataSource dataSource() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean(name="sqlSessionFactory")
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setVfs(SpringBootVFS.class);

        sessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/Configuration.xml"));
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sessionFactoryBean;
    }
    @Bean(name="sqlSessionTemplate")
    public SqlSessionTemplate sqlSession(SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

application.properties

여기에는 web.xml을 제거하고 application.properties에

server.port=8080
server.servlet.context-path=/

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
spring.mvc.static-path-pattern=/resources/static/**

spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/테이블명
spring.datasource.username=계정명
spring.datasource.password=비밀번호

이렇게 진행했다. 그래서 파일 구조도 resources의 static 안으로 js, css 등 여러 정적 파일들을 넣어주고 불러오는 것으로 경로를 잡았다.

그러면서 동시에 WEB-INF폴더는 src/main/webapp 하위에 넣어주고 밖으로 빠져있던 jsp파일도 안으로 넣어주었다.

Tomcat

org.apache.catalina.webresources.Cache.getResource []에 위치한 리소스를 웹 애플리케이션 []을(를) 위한 캐시에 추가할 수 없습니다.
이런 에러도 Tomcat 구동시에 에러를 뱉었는데
이것은
tomcat경로중 apache-tomcat/conf/context.xml
아래와 같은 문구를 넣어준다.

<Resources cachingAllowed="true" cacheMaxSize="100000" />

이렇게 보면 단순한 에러들이었는데 Mybatis가 얽히고 조금만 건드리면 바로 에러가 나고 마이그레이션 경험이 생겨서 좋은것 같다. 앞으로도 레거시를 업데이트 해야할 경우에 오류들을 상기시키면서 잘 진행해보면 한번 더 성장할 수 있을것 같다.

728x90

'Spring' 카테고리의 다른 글

Spring -> Spring Boot 마이그레이션 2  (0) 2022.08.06
Spring Data JPA  (0) 2022.08.06
[Spring] MockMvc Bean 주입 에러  (0) 2022.08.04
[Spring] Spring Security  (0) 2022.08.03
728x90

Mock 빈 오류에 관해서

그동안 혼자 공부한 것들을 토대로 프로젝트를 하나 해보려고 한다.
나름 공부한다고 공부를 했는데 HelloWorld 구현하는데 컨트롤러를 만들어두고 테스트 코드를 내의지대로 처음 작성해봤다.
MockMvc 클래스를 사용하여 Mock Bean을 주입받으려고 했더니 빈이 등록되지 않았다는 오류를 받았다.
일단 오류에는 두가지가 있었다.
@SpringBootTest 어노테이션을 사용할때 @WebMvcTest도 같이 사용해서 MockBean을 주입받는줄 알고 바로 넣었는데 두 Mock객체가 공유되어있는 객체가 아니고 서로 다른 객체이다. 그래서 두개의 빈이 충돌이 발생한다.

여기서 해결책은 두가지였다.

  • @SpringBootTest@AutoConfigurationMockMvc를 사용한 후 MockMvc의 @Autowired를 제거 하는 방법
  • @ExtendWith, @WebMvcTest@Autowired를 같이 사용하는 방법

이렇게 두가지가 있었다.

먼저 @SpringBootTest의 경우 일반적인 테스트로 슬라이싱을 사용하지 않기 때문에 전체 응용 프로그램 Context를 시작한다. 때문에 전체 응용 프로그램을 불러서 모든 bean을 주입하기 때문에 속도가 느리다고 한다.

@WebMvcTest의 경우는 뒤에 ()를 사용해 특정 레이어를 테스트하고 모의 객체를 사용하기 때문에 필요한 bean을 직접 세팅해줘야 하는 단점이 있지만, 가볍고 빠르게 테스트 할 수 있다.

위의 두가지중 아래의 방법에서는 같이 @AutoConfigurationMockMvc를 사용이 가능하다.

가짜 객체인 Mock을 사용할 때 bean 주입하는데에 있어서 이것을 생각하고 주입해주도록 하자.

728x90

'Spring' 카테고리의 다른 글

Spring Data JPA  (0) 2022.08.06
Spring -> Spring Boot 마이그레이션  (0) 2022.08.05
[Spring] Spring Security  (0) 2022.08.03
의존성 주입 어노테이션 정리  (0) 2022.08.03
728x90

스프링 시큐리티

스프링 시큐리티는 스프링 기반의 애플리케이션의 보안(인증, 권한, 인가 등) 을 담당하는 스프링 하위 프레임워크이다.
인증인과를 담당하는 프레임워크이다.

🔒스프링 시큐리티 특징과 구조

  • 보안과 관련하여 체계적으로 많은 옵션을 제공하여 편리하게 사용할 수 있다

  • Filter 기반으로 동작하여 MVC와 분리하여 관리 동작

  • 어노테이션을 통한 간단설정

  • 세션과 쿠키 방식으로 인증

  • 인증관리자(Authentication Manager)와 접근 결정 관리자(Access Decision Manager)를 통해 사용자의 리소스 접근을 관리

어떤 유저가 로그인을 했을 시에 기본 회원과 admin 유저가 있다고 가정을 하면 회원인지 admin인지 인증 관리자가 먼저 판단을 한다. 그래서 누구인지 명확하게 알고 이 로그인한 유저에 대한 권한을 접근 결정 관리자가 판단을 하는데 여기서 특정 기능에 대한 권한이 없다면 그 동작은 실행시켜주지 않게끔 동작을 수행한다.

  • 인증 관리자는 UsenamePasswordAuthenticationFilter, 접근 결정 관리자는 FilterSecurityInterceptor가 수행한다.

🔒스프링 시큐리티 기본 구조
스프링 시큐리티 기본 구조

📢각 필터별 기능 설명

필터 기능
SecurityContextPersistenceFilter SecurityContextRepository에서 SecurityContext를 로드하고 저장하는 일을 담당함
LogoutFilter 로그아웃 URL로 지정된 가상URL에 대한 요청을 감시하고 매칭되는 요청이 있으면 사용자를 로그아웃 시킴
UsernamePasswordAuthenticationFilter 사용자 명과 비밀번호로 이뤄진 폼기반 인증에 사용하는 가상 URL요청을 감시하고 요청이 있으면 사용자의 인증을 진행함
DefaultLoginPageGeneratingFilter 폼기반 또는 OpenId 기반 인증에 사용하는 가상URL에 대한 요청을 감시하고 로그인 폼 기능을 수행하는데 필요한 HTML을 생성한다
BasicAuthenticationFilter HTTP 기본 인증 헤더를 감시하고 이를 처리함
RequestCacheAwareFilter 로그인 성공 이후 인증 요청에 의해 가로채어진 사용자의 원래 요청을 재구성하는데 사용됨 --> 현재 요청과 관련있는 캐시 요청이 있는지 확인하고 있다면 캐시요청을 처리해줌
AnonymousAuthenticationFilter 이 필터가 호출되는 시점까지 사용자가 아직 인증을 받지 못했다면 요청 관련 인증 토큰에서 사용자가 익명 사용자로 나타나게 됨
SessionManagementFilter 인증된 주체를 바탕으로 세션 트래킹을 처리해 단일 주체와 관련한 모든 세션들이 트래킹되도록 도움
ExceptionTranslationFilter 보호된 요청을 처리하는 동안 발생할 수 있는 기대한 예외의 기본 라우팅과 위임을 처리
FilterSecurityInterceptor 권한부여와 관련한 결정을 AccessDecisionManager에게 위임해 권한부여 결정 및 접근 제어 결정을 쉽게 만들어 줌
728x90

'Spring' 카테고리의 다른 글

Spring -> Spring Boot 마이그레이션  (0) 2022.08.05
[Spring] MockMvc Bean 주입 에러  (0) 2022.08.04
의존성 주입 어노테이션 정리  (0) 2022.08.03
Spring Test MockMvc 한글 깨짐 처리  (0) 2022.08.03
728x90

@Resource, @Autowired, @Inject 차이점 정리

세 개의 어노테이션은 컨테이너에 생성된 빈(Bean) 객체를 자동으로 주입받을 수 있도록 해주는 어노테이션들이다. 주의할 점은 Bean 객체가 생성될 때 어노테이션을 스캔해서 자동 주입해준다. 일반적인 방법으로 해당 클래스의 instance를 new해서 생성하면 어노테이션은 작동하지 않는다.
그냥 Bean 설정 파일에서 하나하나 ref="다른bean" 을 생략할수 있다. 설정파일에서 Bean을 등록하지 않고 어노테이션을 통해(@Bean) 등록할 수도 있는데 원리는 같다.

@Resource

Java에서 지원하는 어노테이션이며 특정 프레임워크에 종속적이지 않다.
순서는 아래와 같다.

이름 > 타입 > @Qualifier > 성공

name 속성의 이름을 기준으로 찾는다. 없으면 타입, 없으면 @Qualifier 어노테이션의 유무를 찾고 그 어노테이션이 붙은 속성에 의존성을 주입한다.

context:annotation-config/ 구문을 꼭 xml에 추가해야한다.

사용할 수 있는 위치
멤버변수, setter메소드

@Autowired

Spring에서 지원하는 어노테이션
찾는 순서

타입 > 이름 > @Qualifier > 성공

@Autowired는 주입하려고 하는 객체의 타입이 일치하는지를 찾고 객체를 자동으로 주입한다.
만약 타입이 존재하지 않는다면 @Autowired에 위치한 속성명이 일치하는 bean을 컨테이너에서 찾는다. 그리고 이름이 없을 경우 @Qualifier 어노테이션의 유무를 찾아 그 어노테이션이 붙은 속성에 의존성을 주입해준다.

context:annotation-config/ 구문을 꼭 xml에 추가해야한다.

사용할 수 있는 위치
생성자, 멤버변수, setter 메소드에 적용이 가능함.

스프링 4.3버전 이후부터 생성자가 1개일경우 @Autowired 어노테이션을 생략이 가능하다.

@Inject

Java에서 지원하는 어노테이션이다. 특정 프레임워크에 종속적이지 않다.
찾는 순서

타입 > @Qualifier > 이름 > 실패

@Autowired와 동일하게 작동은 하지만 찾는 순서가 다르다.
@Inject를 사용하기 위해선 maven이나 gradle에 javax 라이브러리 의존성을 추가해야한다.

사용할 수 있는 위치
멤버변수, setter 메소드, 생성자, 일반 메소드에 적용 가능

@Qualifier

만약에 타입이 동일한 bean객체가 여러개가 있을시에 Spring이 Exception을 일으킨다.
스프링이 어떤 bean을 어떤것에 주입해야될지 모르기 때문에 스프링 컨테이너를 초기화하면서 에러를 발생시킨다.

  • @Autowired의 주입대상이 한개여야하는데 실제로 두개 이상의 Bean이 존재하여 주입할때 객체를 선택할 수 없다.
  • 단, @Autowired가 적용된 필드나 설정 메소드의 property 이름과 같은 이름을 가진 Bean객체가 존재할 경우에는 이름이 같은 Bean 객체를 주입받는다.

Exception
@Qualifier에 지정한 Bean 한정자 값을 갖는 Bean이 존재하지 않으면 Exception 발생함.

정리

@Resource @Autowired @Inject
범용 Java에서 지원 Spring 전용 Java에서 지원
연결방식 이름으로 매핑 타입에 맞춰서 연결 타입에 맞춰서 연결
728x90

'Spring' 카테고리의 다른 글

Spring -> Spring Boot 마이그레이션  (0) 2022.08.05
[Spring] MockMvc Bean 주입 에러  (0) 2022.08.04
[Spring] Spring Security  (0) 2022.08.03
Spring Test MockMvc 한글 깨짐 처리  (0) 2022.08.03
728x90

Spring Test MockMvc의 한글 깨짐 처리

스프링에서 테스트 코드를 작성할 때 MockMvc를 흔히 사용한다.

대략 아래와 같이 설정하고 사용한다.

변경일자 2022-04-19 수정
MockMvc를 보통 테스트코드를 작성할 때 사용할텐데,

@WebMvcTest를 사용해서 mvc를 위한 테스트를 만들 수 있다.

//@SpringBootTest  
@WebMvcTest(ApiController.class)  
public class ApiControllerTest {  

    private MockMvc mockMvc;  

    @Autowired  
    private WebApplicationContext ctx;  

    @BeforeEach  
    public void setup() {  
        this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx)  
                .alwaysDo(print())  
                .build();  
    }  

    @Test  
    public void aaa() throws Exception {  
        String keyword = "sports";  

        MvcResult result = this.mockMvc  
                .perform(  
                        get("/api/search/" + keyword)  
                )  
                .andExpect(status().isOk());  
    }  
}  

위의 테스트 코드에서는 한글이 없으므로 아무 문제가 없는데, 아래와 같이 한글을 사용하면 깨진 한글이 Controller에 유입될 수 있으며, 결국 원하는 대로 동작하지 않게 된다.

    @Test  
    public void aaa() throws Exception {  
        String keyword = "스포츠";  // 한글 사용  

        MvcResult result = this.mockMvc  
                .perform(get("/api/search/" + keyword))  
                .andExpect(status().isOk());  
    }  

이 문제는 `MockMvc`를 설정할 때 `CharacterEncodingFilter`를 추가해주면 쉽게 해결할 수 있다.

    @Before  
    public void setup() {  
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)  
                .addFilters(new CharacterEncodingFilter("UTF-8", true))  // 필터 추가  
                .alwaysDo(print())  
                .build();  
    }  
728x90

'Spring' 카테고리의 다른 글

Spring -> Spring Boot 마이그레이션  (0) 2022.08.05
[Spring] MockMvc Bean 주입 에러  (0) 2022.08.04
[Spring] Spring Security  (0) 2022.08.03
의존성 주입 어노테이션 정리  (0) 2022.08.03

+ Recent posts