Search

'spring'에 해당되는 글 3건

  1. 2021.09.06 IoC container and Bean
  2. 2021.09.06 @Bean vs @Component
  3. 2021.03.05 Spring 테이블 칼럼이 아닌 필드 데이터 받아오기

IoC container and Bean

개발/spring 2021. 9. 6. 21:00 Posted by 아는 개발자

IoC Containner and Bean

 

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC 
container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

 

IoC Container는 Spring에서 객체 의존성을 대신 관리해주는 플랫폼이고 이 플랫폼 내에서 생성되고(instantiated) 조립되고(assembled) 관리되는(managed) 객체를 Bean 이라고 한다. 간단하게 Bean은 애플리케이션 내에서 존재하느 수 많은 객체인데 Spring Container에서 관리되고 있다고 보면 된다. Spring 을 사용하면 Spring IoC 컨테이너에서 객체의 생성과 의존성 주입을 관리 할 수 있다. 

Application Context

 

The interface org.springframework.context.ApplicationContext represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the aforementioned beans. The container gets its instructions on what objects to instantiate, configure, and assemble by reading configuration metadata. The configuration metadata is represented in XML, Java annotations, or Java code. It allows you to express the objects that compose your application and the rich interdependencies between such objects.

 

ApplicationContext 인터페이스를 통해 Spring IoC container를 만들수 있고, 이 인터페이스는 Bean 객체를 생성하고(instantiating) 설정하고 (configuration) 조립하는 (assembling)한다. 설정 metadata를 읽어서 이 작업을 처리하는데 meta data 포맷은 XML이나 Java 어노테이션 또는 자바 코드를 통해서 가능하다.  

 

Dependencies 

 

Dependency injection (DI) is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes, or the Service Locator pattern.

 

Dependency Injection은 객체 간의 의존성을 정의하는 작업인데 즉 다른 객체간 어떻게 일하는지 의존하는 관계를 객체의 생성자나 setter 함수를 통해서 정해줄 수 있다. Container는 Bean 객체를 생성할 때 이 의존성을 대신 주입해준다. 이 과정은 완전히 역전된 관계라서 Inversion of Control 이라고 부른다. Dependency Injection을 사용하면 코드가 깔끔해지고 디커플링도 효율적으로 수행할 수 있게돼 결과적으로 클래스가 테스트하기 쉬워진다.

 

DI 방법은 생성자를 이용하는 방법과 Setter 함수 기반이 있다.

 

 

https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/beans.html

 

5. The IoC container

The BeanFactory provides the underlying basis for Spring's IoC functionality but it is only used directly in integration with other third-party frameworks and is now largely historical in nature for most users of Spring. The BeanFactory and related interfa

docs.spring.io

 

728x90

'개발 > spring' 카테고리의 다른 글

IoC container and Bean  (0) 2021.09.06
@Bean vs @Component  (0) 2021.09.06
Node.js vs Spring Boot  (5) 2021.03.13
Spring 테이블 칼럼이 아닌 필드 데이터 받아오기  (0) 2021.03.05

@Bean vs @Component

개발/spring 2021. 9. 6. 20:00 Posted by 아는 개발자

Spring IoC 컨테이너 내에서 관리하는 객체들을 Bean 이라고 하고 Bean으로 사용 될 수 있는 객체는 XML이나 코드상에서 지정이 가능하다. 어노테이션을 사용하는 경우 @Bean, @Conponent 같은 어노테이션을 이용해 Spring IoC 컨테이너에 클래스를 객체로 등록할 수 있다. 

 

@Bean 

 

@Bean 어노테이션은 method 레벨 어노테이션으로 개발자가 수정할 수 없는 3rd 라이브러리 객체를 IoC Container에 등록하고 싶을 때 사용한다. 예를 들면 아래 코드처럼 애플리캐이션 전체에서 공통적으로 사용하고 싶은 Kafka 클래스를 만드려는 경우 아래와 같은 코드로 Bean을 등록 할 수 있다. @Bean을 사용할 때는 @Configuration 어노테이션이 추가된 클래스 내부에서 생성 할 수 있다. 

 

@Configuration
public class KafkaConfig {
    ...
    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

 

@Component 

 

@Component는 개발자가 직접 작성한 클래스를 Bean으로 등록할 수 있는 방법이다. 아래 코드처럼 공통적으로 사용하고 싶은 클래스가 있다면 클래스 이름 위에 @Component 어노테이션을 붙여준다. 

 

@Component
class Utils {
   fun print() { }
}

 

차이점

 

@Bean과 @Component를 헷갈릴 수 있는데 Third party 라이브러리를 Bean으로 등록하는 경우에는 @Bean을 사용하고 그렇지 않은 경우에는 @Component로 사용한다고 생각하면 쉽다. 사실 이럴수 밖에 없는 것이 @Component는 클래스 이름 위에서 선언이 가능한데 Third party 라이브러리는 클래스 수정이 불가능하므로 method level 어노테이션인 @Bean을 사용할 수밖에 없다.

728x90

'개발 > spring' 카테고리의 다른 글

IoC container and Bean  (0) 2021.09.06
@Bean vs @Component  (0) 2021.09.06
Node.js vs Spring Boot  (5) 2021.03.13
Spring 테이블 칼럼이 아닌 필드 데이터 받아오기  (0) 2021.03.05

Spring으로 쿼리를 만들다보면 여러개의 테이블을 조인한 쿼리에서 다른 테이블 칼럼의 값까지 읽어올 필요가 있다. 예를 들면 글정보를 받아 오는 api가 있는데 내가 그 글을 좋아요 했는지, 안했는지 유무까지 알려주는 요구 사항의 경우 두 개의 테이블을 조인해야한다.

 

쿼리문을 짜면 다음과 같다. tb_post에 있는 모든 필드를 가져오고, 좋아요 유무는 liked 필드 이름으로 받아오는 것으로 뒀다.

select tb_post.*, when tb_post_user_like.post_id > 0 then true else false end as liked from tb_post
left join tb_post_like on (tb_post.post_id = tb_post_user_like.post_id and tb_post_like.user_id = :findUserId)
where tb_post.post_id = :postId

 

JPA에서 제공하는 CrudRepository 로는 이미 있는 테이블의 칼럼을 매핑해서 받아오는데 반해 이 방법은 임의로 liked라는 새 칼럼을 생성한 것이기 때문에 테이블과 1:1 매핑된 엔티티 클래스에서 했던 것처럼 칼럼을 자동으로 매핑하는게 안되고 다른 방법을 써야한다. 열심히 구글 검색을 해본 결과 세가지 방법을 발견했다. 

 

1. Object형태로 받아오기 

Query문에서 리턴 값을 Object로 받아오면 모든 필드 값의 리턴을 받아 올 수 있다. 가장 직관적이고 쉬운 방법이다.

 

@Query(value = "select tb_post.*, case when tb_post_like.post_id > 0 then true else false end as liked from tb_post \n" +
        "from tb_post \n" +
        "left join tb_post_like on (tb_post.post_id = tb_post_like.post_id and tb_post_like.user_id = :findUserId)\n" +
        "where tb_post.post_id = :postId", nativeQuery = true)
fun findPostById(@Param("postId") postId: Long, @Param("findUserId") findUserId: Long) : List<Array<Any>>

하지만 이렇게 읽어오면 아래 그림처럼 필드 값이 생략돼서 날라오게 돼서 알아보기가 힘들다. 쿼리문에서 칼럼 필드 순서를 지정하는 방법으로 처리할 수 있으나 알아보기가 힘들어서 관리하기가 어려운 단점이 있어 추천하지 않는다. 위와 같은 형태로 읽어오는 클래스를 만든다면 더더욱 쓰지 않는게 좋다.

 

 

2. JPA New 명령어 

 

JPA 쿼리의 New 명령어를 사용하면 리턴 값을 클래스로 줄 수 있다. 단 이 방법은 native query 문을 사용하지 못하고 jpa 쿼리를 사용해야 한다는 점이다. limit을 쓰는 구문에서는 사용할 수 없다.

 

data class Post(postId: Long, postTitle: String, postContent: String, liked:Boolean)

@Query(value = "select new com.package.Post(tb_post.post_id, tb_post.post_title, tb_post.post_content, case when tb_post_like.post_id > 0 then true else false end) from tb_post from tb_post left join tb_post_like on (tb_post.post_id = tb_post_like.post_id and tb_post_like.user_id = :findUserId) where tb_post.post_id = :postId")
fun findPostById(@Param("postId") postId: Long, @Param("findUserId") findUserId: Long) : List<Post>

 

3. SetQueryResultSetMapping 

 

쿼리에서 읽어온 컬럼 필드를 클래스에 매핑 해줄 수 있는 어노테이션이다. ConstructorResult 어노테이션에서 칼럼 필드 값을 읽어와서 Post 클래스의 생성자로 만들 수 있다. 하단의 NamedNativeQuery 어노테이션에서는 쿼리의 이름을 정하고, ConstructorResult에서 참조하는 필드의 형태로 읽어 올 수 있도록 Select 문을 만들어 주고 사용할 mapping을 SqlResultSetMapping에서 지정한 이름과 동일한 값을 입력한다. 이렇게 하면 이 쿼리는 자동으로 Post 클래스 값을 출력하는 쿼리가 된다.

 

@SqlResultSetMapping(
        name = "PostMapping",
        classes = [
                ConstructorResult(
                        targetClass = Post::class,
                        columns = [
                                ColumnResult(name = "post_id", type = Long::class),
                                ColumnResult(name = "post_title", type = String::class),
                                ColumnResult(name = "post_content", type = String::class),
                                ColumnResult(name = "liked", type = Boolean::class)
                        ]
                )
        ]
)
@NamedNativeQueries(value = [
    NamedNativeQuery(name = "findPostByIdBaseOnUser", query = "select tb_post.post_id, tb_post.post_title, tb_post.post_content, case when tb_post_like.post_id > 0 then true else false end as liked from tb_post \n" +
            "left join tb_post_like on (tb_post.post_id = tb_post_like.post_id and tb_post_like.user_id = :findUserId)\n" +
            "where tb_post.post_id = :postId resultSetMapping = "PostMapping")
])
@Entity
data class Post(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        val postId: Long,
        @Column
        val postTitle: String,
        @Column
        val postContent: String,
        @Column
        val liked: Boolean = false
): Serializable

 

아래의 코드로 쿼리 호출이 가능하다. 앞서 설저한 NamedNativeQuery의 이름 값과 동일하게 넣는다. 초반에 보일러플레이트 코드가 많지만 Post 클래스를 지속적으로 사용하고자 한다면 필드 값을 참조 할 수 있기 때문에 관리가 더 편리하다. 

 

@Service
class PostService {
    @PersistenceContext
    lateinit var entityManager: EntityManager

    fun postById(postId: Long, findUserId: Long): Post? {
        return entityManager.createNamedQuery("findPostByIdBaseOnUser", Post::class.java)
                .setParameter("postId", postId)
                .setParameter("findUserId", findUserId)
                .resultList
                .firstOrNull()

    }
}
728x90

'개발 > spring' 카테고리의 다른 글

IoC container and Bean  (0) 2021.09.06
@Bean vs @Component  (0) 2021.09.06
Node.js vs Spring Boot  (5) 2021.03.13
Spring 테이블 칼럼이 아닌 필드 데이터 받아오기  (0) 2021.03.05