네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'web/Spring'에 해당되는 글 67건


반응형

Fetch Join 


JPQL(Java Persistence Query Language)에는 경로 표현식이 있다.

경로 표현식이란 점(.)으로 객체 그래프를 탐색하는 것이다.

SELECT m.name FROM Member m  // 상태 필드
SELECT m.team FROM Member t  // 단일 값 연관 경로
SELECT t.members FROM Team t  // 컬렉션 값 연관 경로

* 상태 필드(state field): 단순하게 값을 저장하는 필드

* 연관 필드: 연관 관계를 위한 필드

    - 단일 값 연관 필드: @ManyToOne, @OneToOne 처럼 xxxToOne 관계, 대상이 엔티티

    - 컬렉션 값 연관 필드: @ManyToMany, @OneToMany 처럼 xxxToMany 관계, 대상이 컬렉션

 

[경로 표현식 특징]

* 상태 필드: 경로 탐색의 끝으로, 더이상 탐색이 불가능하다.

* 단일 값 연관 경로: 묵시적 내부 조인이 발생하며 추가적인 경로 표현식으로 탐색이 가능

* 컬렉션 값 연관 경로: 묵시적 내부 조인이 발생하며 더이상 탐색이 불가하다.

    - From 절에서 명시적 조인을 통해 별칭(alias)을 얻으면 별칭을 통해 탐색이 가능하다.

 

** 되도록 묵시적 내부 조인이 되도록 JPQL을 작성하지 않도록 JPQL을 작성하지 말 것.

묵시적 내부 조인이 발생하면 의도치 않은 조인 쿼리가 발생할 수 있으며 발견하기 어렵다. 성능과 연관이 있을 수 있기 때문에 명시적 조인을 사용한다.

 

[상태 필드 경로 탐색]

List<Integer> members = em.createQuery("SELECT m.age FROM Member m", Integer.class).getResultList();

* Hibernate

select m.age from Member m

* SQL

select member0_.age as col_0_0_ from Member member0_

 

- 상태 필드는 추가적인 조인이 없다.

 

[단일 값 연관 경로 탐색]

List<Team> teams = em.createQuery("SELECT m.team FROM Member m", Team.class).getResultList();

* Hibernate

select m.team from Member m

* SQL

select

    team1_.team.id as team_id1_3_, team1_.name as name2_3_

from

    Member member0_

    inner join

        Team team1_ on member0_.team_id = team1_.team_id

 

- 단일 값 연관 경로는 탐색 시 묵시적 내부 조인이 발생한다.

 

[컬렉션 값 연관 경로 탐색]

// 컬렉션 값을 조회 시, TypedQuery 제네릭을 Collection으로 지정해야 한다.
List<Collection> resultList = em.createQuery("SELECT t.members FROM Team t", Collection.class)
                                .getResultList();

* Hibernate

select t.members from Team t

* SQL

select 

    members1_.member_id as member_i1_0_, members1_.age as age2_0_, members1_.team_id as team_id4_0_, members1_.username as username3_0_

from 

    Team team0_

    inner join

        Member members1_ on team0_team_id = members1_.team_id

 

- 컬렉션 값 연관 경로는 탐색 시 묵시적 내부 조인이 발생하며 값을 받는 클래스 타입을 'Collection'으로 지정해야 한다.

 

** 묵시적 조인 시 주의사항

- 항상 내부 조인이 일어난다.

- 컬렉션은 경로 탐색의 끝으로, 더이상 점을 찍어 탐색이 불가하다. alias를 사용하여 경로 탐색을 계속 할 수 있다.

- 경로 탐색은 주로 select, where 절에서 사용되나, 묵시적 조인으로 인해 SQL의 FROM(Join)에 영향을 준다.

 

 


Fetch Join (페치 조인)

JPQL에서 지원하는 기능으로 '성능 최적화'를 위해 사용한다.

연관된 엔티티나 컬렉션을 SQL로 한번에 조회하는 기능이다.

JOIN FETCH 명령어를 사용한다.

SELECT m FROM Member m JOIN FETCH m.team

 

- 즉시 로딩처럼 동작한다.

 

[페치 조인과 일반 조인 차이점]

 

 가정  Member:Team = N:1 다대일 관계일 때, fetch 전략을 EAGER (즉시 로딩)으로 설정했다

Member member = em.find(Member.class, 5L);
membe.getTeam().getName();

* SQL

select 

    member0_.member_id as member_i1_0_0_, member0_.age as age2_0_0_, member0_.team_id as team_id4_0_0_, member0_.username as username3_0_0_, team1_.team_id as team_id1_3_1_, team1_.name as name2_3_1_

from

    Member member0_

left outer join Team team1_

on member0_.team_id = team1_.team_id

where member0_.member_id = ?

 

=> 즉시 로딩으로 설정했기 때문에 em.find()할 때 Member에 연관된 Team까지 조회해서 영속성 컨텍스트에 저장한다.

 

 문제  일반 JOIN

Member member = em.createQuery("SELECT m FROM Member m JOIN m.team WHERE m.id = :id", Member.class)
                  .setParameter("id", 5L)
                  .getSingleResult();
                  
member.getTeam().getName();

* Hibernate

SELECT m FROM Member m JOIN m.team WHERE m.id = :id

 

* SQL

select 

    member0_.member_id as member_i1_0_, member0_.age as age2_0_, member0_.team_id as team_id4_0_, member0_.username as username3_0_

from

    Member member0_

left outer join Team team1_

on member0_.team_id = team1_.team_id

where member0_.member_id = ?

 

select

    team0_.team_id as team_id1_3_0_, team0_.name as name2_3_0_

from

    Team team0_

where

    team0_team_id = ?

 

JPQL로 명시적 조인을 사용하여 Member 와 Team을 한번에 가져왔다. Member와 Team은 즉시로딩으로 설정되어 있다.

하지만, SQL이 두 번 출력되는 문제가 발생한다.

 

JPQL에서 Member만 select 하고 Team은 select 하지 않았기 때문에 Member 엔티티에만 값이 채워져 있고 Team 엔티티는 비어있는 상태다.

team을 구하고 실제로 값을 사용할 때(위 코드에서 member.getTeam().getName()에서)

select 쿼리로 Team 엔티티를 가져온다.

=> 쿼리가 2번이 나간다 => N+1 문제가 발생한다.

 

 해결  페치 조인 사용 또는 명시적 조인

페치 조인

SELECT m from Member m JOIN FETCH m.team WHERE m.id = :id

명시적 조인

SELECT m, t FROM Member m JOIN m.team t WHERE m.id = :id

 

 결론 

일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않는다.

페치 조인 실행 시 연관된 엔티티를 함께 조회한다.

 

[페치 조인을 사용하는 이유]

- 대부분 엔티티 간의 연관관계는 지연로딩(LAZY)로 되어있다. 하지만 상황에 따라 연관된 엔티티를 한번에 함께 끌어외야 할 상황이 존재하기 때문에, 그럴 때 Fetch Join을 사용하여 즉시 로딩(EAGER)처럼 동작하게 한다. (*LAZY를 적용해도 페치 조인이 우선시 된다.)

 

[컬렉션 페치 조인]

- 컬렉션 페치 조인은 일대다 관계에서 사용할 수 있으며 데이터가 많아질 수 있다.

=> DISTINCT로 중복 제거가 가능하다.

 

* SQL의 DISTINCT

- ROW가 완벽히 일치해야 중복 제거

* JPQL의 DISTINCT

- SQL의 DISTINCT 기능 뿐만 아니라, 동일한 엔티티면 중복 제거(Application단에서 엔티티 중복을 제거한다.)

즉, 같은 식별자를 가진 Entity는 중복 제거가 가능하다.

 

[페치 조인의 특징과 한계]

 문제 

- 페치 조인의 대상에는 원칙상 별칭을 줄 수 없다.

    * Hibernate는 가능하지만, 가급적 사용을 지양한다.

- 둘 이상의 컬렉션은 페치 조인을 할 수 없다.

- 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.

    * 일대일, 다대일같은 단일 값 연관 필드들은 페치 조인을 해도 페이징이 가능하다. (데이터 증복이 없음)

    * Hibernate는 경고로그를 남기며 메모리에서 페이징을 한다.

      일단, 전체 데이터를 가져와 메모리에 올려놓고, 메모리에서 N개씩 데이터를 결과로 보여준다. 자칫 OutOfMemory가 발생할 수 있고, 성능에 영향을 준다.

 

 해결 

1) @BatchSize

N+1 문제를 테이블 수 + 1로 줄인다. SQL의 IN 쿼리를 사용한다.

2) JPQL에서 DTO로 조회한다. SELECT new com.jiyoon.dto.TestDTO(...) from Test t ...

 

 결론 

- 모든 것을 페치 조인으로 해결할 수는 없다. QueryDSL을 사용하는 것이 바람직한 경우가 많다.

- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.

- 여러 테이블을 조인해 엔티티가 가진 모양이 다른 결과를 내야한다면, 페치 조인보다 일반 조인이 효과적이고, 필요한 데이터만 조회해 DTO로 반환하는 것이 좋다.

    * FETCH JOIN으로 엔티티 조회

    * Entity를 조회해 DTO로 변환

    * JPQL에서 바로 DTO를 조회

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

JPA 특징 중에 하나인 Criteria Queries

- raw SQL문 없이 객체 지향적으로 쿼리를 작성할 수 있다.

1. Maven Dependencies

pom.xml 파일에 Hibernate 관련 의존성을 추가해준다.

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>   
    <version>5.3.2.Final</version>
</dependency>

참고로 spring-booy-starter-data-jpa에는 hibernate관련 의존성이 들어있다. spring data jpa를 사용하는 경우 hibernate 관련 의존성을 추가하지 않아도 된다.
또, spring boot에서 의존성을 추가할 때는 버전을 입력하지 않아도 spring boot에서 자동으로 버전 관리를 해준다.

spring-boot-starter-data-jpa에 hibernate 관련 의존성이 있다.

2. 예제

Item.java

@Getter
@Setter
public class Item implements Serializable {
    
    private Integer itemId;
    private String itemName;
    private String itemDescription;
    private Integer itemPrice;
}


데이터베이스에서 Item의 모든 row를 가져오는 간단한 criteria query를 작성해보자.

Session session = HibernateUtil.getHibernateSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Item> cr = cb.createQuery(Item.class);
Root<Item> root = cr.from(Item.class);
cr.select(root);

Query<Item> query = session.createQuery(cr);
List<Item> results = query.getResultList();


[where() 메서드를 사용하여 구체적인 조건을 작성]
1. 가격이 1000 초과인 Item을 가져오기

cr.select(root).where(cb.gt(root.get("itemPrice"), 1000)); // 가격이 1000 초과인 Item
cr.select(root).where(cb.lt(root.get("itemPrice"), 1000)); // 가격이 1000 미만인 Item
cr.select(root).where(cb.like(root.get("itemName"), "%chair%")); // 이름에 chair를 포함하는 Item
cr.select(root).where(cb.between(root.get("itemPrice"), 100, 200)); // 가격이 100과 200 사이인 Item
cr.select(root).where(root.get("itemName").in("Skate Board", "Paint", "Glue")); // 이름이 Skate Board 거나 Paint 거나 Glue인 Item
cr.select(root).where(cb.isNull(root.get("itemDescription"))); // 설명이

2. 가격이 1000 미만인 Item 가져오기

cr.select(root).where(cb.lt(root.get("itemPrice"), 1000));

3. 가격이 100과 200 사이인 Item 가져오기

cr.select(root).where(cb.between(root.get("itemPrice"), 100, 200));

4. 이름이 Skate Board, Paint, Glue인 Item 가져오기

cr.select(root).where(root.get("itemName").in("Skate Board", "Paint", "Glue"));

5. 두개 이상의 조건이 있을 땐 Predicate를 사용한다.

Predicate[] predicates = new Predicate[2];
predicates[0] = cb.isNull(root.get("itemDescription"));
predicates[1] = cb.like(root.get("itemName"), "%chair%");
cr.select(root).where(predicates);
Predicate greaterThanPrice = cb.gt(root.get("itemPrice"), 1000));
Predicate chairItems = cb.like(root.get("itemName"), "%chair%");

cr.select(root).where(cb.or(greaterThanPrice, chairItems));
cr.select(root).where(cb.and(greaterThanPrice, chairItems));

[orderBy(), asc(), desc() 메서드로 sorting]

cr.orderBy(cb.asc(root.get("itemName")), cb.desc(root.get("itemPrice")));

[projections, aggregates, grouping function]

CriteriaQuery<Long> cr = cb.createQuery(Long.class);
Root<Item> root = cr.from(Item.class);
cr.select(cb.count(root));
Query<Long> query = session.createQuery(cr);
List<Long> itemProjected = query.getResultList();


CriteriaUpdate(JPA 2.1부터)

CriteriaUpdate<Item> criteriaUpdate = cb.createCriteriaUpdate(Item.class);
Root<Item> root = criteriaUpdate.from(Item.class);
criteriaUpdate.set("itemPrice", newPrice);
criteriaUpdate.where(cb.equal(root.get("itemPrice"), oldPrice));

Transaction transaction = session.beginTransaction();
session.createQuery(criteriaUpdate).executeUpdate();
transactionl.commit();

set() 메서드를 사용해서 새로운 값을 넣어준다.

CriteriaDelete

CriteriaDelete<Item> criteriaDelete = cb.createCriteriaDelete(Item.class);
Root<Item> root = criteriaDelete.from(Item.class);
criteriaDelete.where(cb.greaterThan(root.get("itemPrice"), targetPrice));

Transaction transaction = session.beginTransaction();
session.createQuery(criteriaDeleter).executeUpdate();
transaction.commit();








728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

엔티티에서 Boolean형을 Database에서는 'Y' 또는 'N'으로 Char(1) 형으로 처리하고 싶을 때 @Converter 어노테이션으 사용하여 해결한다.

1. Converter 객체 생성

BooleanToYNConverter.java

@Converter
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
    
    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        return (attribute != null&& attribute) ? "Y" : "N";
    }
    
    @Override
    public Boolean convertToEntityAttribute(String yn) {
        return "Y".equalsIgnoreCase(yn);
    }
}


* AttributeConverter 인터페이스

public interface AttributeConverter<X, Y> {
    
    Y convertToDatabaseColumn(X var1);
    
    X convertToEntityAttribute(Y var1);
}


2. Entity에 Converter 적용

적용하고 싶은 필드에 @Convert 어노테이션을 붙인다.

@Entity
@Table
@Getter
public class Member {

    // ... 중략 ...
    
    @Covert(converter = BooleanToYNConverter.class)
    private boolean isActive;
}








728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

[Thymeleaf 의 String API]

String 객체의 유틸리티 메서드는 #strings 이다.

 

문자열 비교하는 메서드

${#strings.equals(str1, str2)}
$(#strings.equalsIgnoreCase(str1, str2)}

Null-safe String 변환 메서드

${#strings.toString(obj)}

- 배열(array), 리스트(list), 셋(set)에도 잘 동작한다.

 

값(value)이 존재하는지 검사하는 메서드

${#strings.isEmpty(str)}
${#strings.arrayIsEmpty(arr)}
${#strings.listIsEmpty(list)}
${#strings.setIsEmpty(set)}

- true 또는 false로 값을 반환한다.

 

디폴트 값 설정

 

${#strings.defaultString(text,default)}
${#strings.arrayDefaultString(textArr,default)}
${#strings.listDefaultString(textList,default)}
${#strings.setDefaultString(textSet,default)}

 

 

출처

https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#strings

 

Tutorial: Using Thymeleaf

1 Introducing Thymeleaf 1.1 What is Thymeleaf? Thymeleaf is a Java library. It is an XML/XHTML/HTML5 template engine able to apply a set of transformations to template files in order to display data and/or text produced by your applications. It is better s

www.thymeleaf.org

https://cizz3007.github.io/%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84/syntax/thymeleaf/2018/04/10/thymeleaf2/

 

thymeleaf 문법

Thymeleaf 문법 2

cizz3007.github.io

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

1. 설정 값 분리의 필요성

개발을 진행하다 보면 데이터베이스 연결 정보나 외부 API 주소 등과 같은 메타 정보들을 관리해야 한다. 해당 메타 정보들을 클래스 파일에 넣을 수도 있겠지만 다음과 같은 이유로 프로퍼티 파일(properties)나 야믈파일(yml)로 분리해서 관리할 것을 권장한다. 

 

1. 환경에 따라 유연하게 값을 설정할 수 있음.

- 일반적으로 로컬 환경이나 알파/스테이지 환경, 운영환경에 따라 서로 다른 데이터베이스 서버를 가지고 있다. 만약 환경에 따라 다른 값을 자바 코드만으로 설정해주려면 상당히 번거롭고 중복되는 코드가 상당히 많아진다. 그래서 설정값은 별도의 파일로 분리하고, 자바 코드에서는 환경 정보에 맞는 설정값을 불러오도록 한다.

2. 초기값을 설정할 수 있음.

불러오는 설정 값에 초기값을 지정해줄 수 있는데, 이를 통해 설정한 값이 불러지지 않은 경우에 대비할 수 있으며 테스트 작성을 용이하게 할 수 있다. 즉, 초기값을 설정함으로써 테스트 작성에 유연하고 안전하게 개발할 수 있다.

3. 불필요한 컴파일을 줄일 수 있음.

만약 프로퍼티를 분리하지 않는다면 값이 클래스 안에 있을 것이다. 그럴 때 만약 값이 수정되면 소스코드가 변하는 것이므로 다시 컴파일을 진행해주어야 한다. 하지만 설정 파일을 분리하고 @Value로 참조하게 한다면 컴파일없이 주입되는 값을 변경할 수 있다.

 

2. @Value의 사용법과 처리 및 동작 과정

[@Value의 사용법]

@Value로 메타정보를 가져오는 방법으로는 PropertyPlaceHolderConfigurer를 통해 ${}을 사용하는 방식과 SpEL을 통해 #{}을 이용하여 가져오는 방식, 크게 2가지가 있다.

 

1. PropertyPlaceHolderConfigurer를 통한 수동 변환

PropertyPlaceHolderConfigurer은 빈 팩토리 후처리기로써 매번 빈 오브젝트가 만들어진 직후에 오브젝트의 내용이나 오브젝트 자체를 변경하는 빈 후처리기와 달리 빈 설정 메타정보가 모두 준비됐을 때 빈 메타정보 자체를 조작하기 위해 사용된다.

예를 들어 database.username과 같이 properties에 작성된 키 값을 ${}안에 넣어주면 Spring이 PropertyPlaceHolderConfigurer를 통해 초기화 작업 중에 해당 값을 실제 값으로 치환한다.

 

database.username= jiyoon
database.password= password
@Value("${database.username}")
private String username;

@Value("${database.password}")
private String password;

하지만 이러한 방법은 대체할 위치를 치환자로 지정해두고, 별도의 후처리기가 값을 변경해주기를 기대하기 때문에 수동적이다. 그래서 초기 빈 메타정보에는 ${database.username}과 같은 문자열이 등록되어 있다. 그러다가 스프링 컨테이너가 설정 파일에서 대체할 적절한 키 값을 찾지 못하면 해당 문자열이 그대로 남아있게 된다. 그래서 치환자의 값이 변경되지 않더라도 예외가 발생하지 않으므로 SpEL을 통한 능동 변환을 권장한다. 초기에는 PropertyPlaceHolderConfigurer를 사용했어야 했지만 Spring 3부터 SpEL을 지원하면서 이를 이용하는 방식이 권장되고 있다. 

물론 최근 Spring의 버전에선 이런 문제를 개선한 PropertySourcesPlaceholderConfigurer가 등장하면서 적절한 값을 찾지 못하면 에러가 발생하도록 수정되었지만 그래도 수동적인 방법보다는 빈에서 값을 꺼내는 SpEL이 권장된다.

 

2. SpEL을 통한 능동 변환

SpEL은 스프링 3.0에서 처음 소개된 스프링 전용 표현식 언어로 이를 통해 다른 빈 오브젝트나 프로퍼티에 손쉽게 접근할 수 있다. SpEL은 기본적으로 #{}안에 표현식을 넣도록 되어있는데, user.name이라는 표현식은 이름이 user인 빈의 name 프로퍼티를 의미한다. SpEL은 일반 프로그래밍 언어 수전에 가까운 강력한 표현식 기능을 지원한다. 다른 빈의 프로퍼티에 접근가능할 뿐만 아니라 메서드 호출도 가능하고 다양한 연산도 지원하며 클래스 정보에도 접근할 수 있다. 심지어 생성자를 호출해서 객체를 생성할 수도 있다.

SpEL(Spring Expression Language)를 이용하는 방법은 프로퍼티의 대체 위치를 설정해두고 빈 팩토리 후 처리기가 바꿔주길 기다리는 수동적인 방법과는 달리 다른 빈 오브젝트에 직접 접근할 수 있는 표현식을 이용해 원하는 프로퍼티 값을 능동적으로 가져온다.

별도의 설정이 없다면 다음과 같이 설정 파일에 작성된 값을 사용할 수 있는 것이다.

database.username= jiyoon
database.password= password
@Value("#{environment['database.username']}")
private String username;

@Value("#{environment['database.password']}")
private String password;

이는 environment라는 이름의 빈으로부터 해당 키에 해당하는 값을 읽어오는 것이다. 여기서 가져오는 Bean은 Properties타입 등의 빈이 될 수 있다.

SpEL을 사용하면 오타와 같은 실수가 있을때 에러 검증이 가능하다는 장점이 있다. 그래서 이름을 잘못 적는 실수가 있어도 예외가 발생하지 않는 ${} 기반의 프로퍼티 치환자 방식보다는 SpEL의 사용을 권장한다.

 

여기서 우리가 참고해야 할 점은 @Value 어노테이션은 스프링 컨테이너가 참조하는 정보를 의미할 뿐, 그 자체로 값을 넣어주는 것은 아니다. 그래서 Spring 컨테이너 밖에서 사용되면 @Value 어노테이션은 무시된다.

 

[타입의 변환]

설정 파일의 내용들은 모두 텍스트로 작성된다. 값을 넣을 프로퍼티 타입이 String이라면 아무런 문제가 없지만 그 외의 타입인 경우는 타입 변환과정이 필요하다.

이를 위해 스프링은 2가지 종류의 타입 변환 서비스를 제공한다.

 

1. PropertyEditory

디폴트로 사용되는 타입변환기는 java.beans의 PropertyEditor 인터페이스를 구현한 것이다. PropertyEditor는 원래 GUI 개발 환경을 위해 개발되었지만 이제는 XML에 작성된 내용이나 @Value의 String 값에서 프로퍼티 타입으로 변경하기 위해 사용된다.

PropertyEditory는 boolean, Boolean, int, Integer 등의 기본 타입의 변환과 배열의 변환 외에도 CharSet, Class, File, Currency, Locale, Pattern, Resource, Timezone, URI, URL 등의 변환을 제공한다.

 

database.url= jdbc:mariadb://localhost/security?characterEncoding=utf-8
@Value("${database.url}")
private URI url;

이렇게 다양한 타입으로의 변환이 가능한 이유는 각각의 타입 변환기를 Spring이 구현해두었기 때문이다. 예를 들어 String을 URI로 바꾸는 경우에 URIEditor, Charset으로 바꾸는 경우에 CharsetEditor가 사용되는 것이다.

만약 Spring이 지원하지 않는 타입의 오브젝트를 직접 값으로 주입하고 싶다면, PropertyEditory 인터페이스를 구현해서 직접 변환기를 구현할 수도 있다. 물론 권장하는 방법은 아니다. 이러한 방법 대신에 빈을 등록하고 DI 받는 방법이 있기 때문이다.

또한 PropertyEditory는 내부적으로 상태값을 관리하기 때문에 멀티 쓰레드 환경에서 사용이 불가능하며 String <-> Object 관계의 변환만 가능하다는 단점이 있다.

 

2. ConversionService

스프링 3부터는 PropertyEditory 대신 사용할 수 있는 ConversionService를 지원하기 시작했다. ConversionService는 자바빈에서 차용해오던 PropertyEditory와 달리 스프링이 직접 제공하는 타입 변환 API이다.

ConversionService는 변환기의 작성이 간편하며 PropertyEditory와 달리 멀티 쓰레드 환경에서 공유해 사용할 수 있다는 장점이 있다. 만약 우리가 conversionService라는 이름의 빈으로 ConversionService 타입의 빈을 등록하면 스프링 컨테이너가 이를 자동 인식해서 PropertyEditor를 대신하여 사용한다.

예를 들어 String을 int로 바꾸는 경우에 직접 만든 Converter를 사용하려면 다음과 같이 구현할 수 있다.

@Configuration
public class ConverterConfig {

    @Bean
    public ConversionServiceFactoryBean conversionService() {
        final ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
        conversionServiceFactoryBean.setConverters(converters());
        
        return conversionServiceFactoryBean;
    }
    
    @Bean
    public Set<Converter> converters() {
        final Set<Converter> set = new HashSet<>();
        set.add(new CustomConverter());
        
        return set;
    }
}
@Component
public class CustomConverter implements Converter<String, Integer> {

    public Integer convert(String username) {
        try {
            return Integer.parseInt(username);
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}

물론 Spring에서 거의 모든 Conversion 기능을 제공해주기 때문에 거의 사용할 일이 없지만 특별하게 값을 변환해주어야 하는 경우라면 위와 같이 CustomConverter를 만들어서 등록하면 된다.

 

 함께보면 좋은 글 

https://frogand.tistory.com/131

 

[Spring Boot] @ConfigurationProperties 사용법

@ConfigurationProperties Spring Boot에서 *.properties나 *.yml 파일에 있는 property를 자바 클래스에 값을 바인딩하여 사용할 수 있게 해주는 어노테이션 Spring Boot에서는 운영에 필요한 정보를 프로퍼티 파..

frogand.tistory.com

 출처 

https://mangkyu.tistory.com/167

 

[Spring] 설정 값 분리의 필요성과 @Value의 사용법 및 동작 과정

이 내용은 토비의 스프링 2권의 1장 내용을 참고하며 작성하였습니다. 1. 설정 값 분리의 필요성 [ 설정 값 분리의 필요성 ] 개발을 진행하다 보면 데이터베이스 연결 정보나 외부 API 주소 등

mangkyu.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !