[Java] 51. Spring 프레임워크에서 JPA 사용법(의존성 주입 @Autowired)


Study/Java  2021. 6. 23. 14:25

안녕하세요. 명월입니다.


이 글은 Spring 프레임워크에서 JPA 사용법(의존성 주입 @Autowired)에 대한 글입니다.


이전 글에서 JPA ORM 프레임워크를 통해서 데이터베이스로부터 데이터를 취득하고 그것을 DAO 클래스를 작성해서 사용하는 방법에 대해 설명했습니다.

링크 - [Java] 50. JPA 프로젝트에서 DAO 클래스 작성하기


이것을 이제 Spring 웹 프레임워크에서 사용하고 싶은데 그냥 DAO 클래스의 인스턴스를 생성해서 사용하는 것도 안되는 것은 아닙니다.

그러나 DAO 클래스라는 것은 여러 인스턴스를 생성해서 사용하는 것보다 Singleton 패턴의 형식으로 한번의 인스턴스를 생성해서 여러번 재사용하는 것이 프로그램의 성능상 유리합니다.

왜냐하면 인스턴스 생성(new)하는 것이 생각보다 시간이 걸립니다.(그렇다고 몇초단위의 시간은 아닙니다.) 그리고 마스터 테이블의 DAO 클래스라면 한번의 취득으로 여러번 재사용하는 것이 훨씬 좋습니다.


그런 것을 생각해서 DAO를 Singleton 패턴으로 바꾸고, 그것을 취득하기 위한 Factory 패턴을 만들면 좋지만 Spring은 그런 것을 지원해 주는 의존성 주입(Dependency Injection)이라는 개념이 있습니다.

이 의존성 주입이라는 것은 Singleton 패턴과 Factory 패턴을 합친 패턴이라고 생각하면 됩니다.(사실 Singleton 패턴과 Factory 패턴을 합친 개념과는 약간 다르기는 합니다만, 거의 비슷합니다. 의존성 주입 패턴은 디자인 패턴 카테고리에서 따로 설명하겠습니다.)


의존성 주입 패턴을 사용하기 위해서는 @Autowired 어노테이션을 알아야 합니다.

@Autowired 어노테이션은 Spring 설정 xml(mvc-config.xml)에서 설정한 bean객체를 가져와서 사용하는 어노테이션입니다. 최초 처음에 호출이 되면 인스턴스가 생성되어서 두번째부터는 생성된 인스턴스를 재사용하는 방법입니다.(Singleton 패턴)


먼저 이전 글에서 작성한 Entity 클래스와 DAO 클래스를 spring 프로젝트로 가져옵니다.

링크 - [Java] 39. Spring web framework를 이용해서 웹 서비스 프로젝트를 만드는 법

먼저 mvc-config.xml를 설정하겠습니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/mvc
                           http://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">
  <!-- 컨트럴러 패키지 설정 -->
  <context:component-scan base-package="controller"/>
  <!-- 서블릿에서 jsp파일을 읽어서 변환하는 인코딩 타입 -->
  <mvc:annotation-driven>
    <mvc:message-converters>
      <bean class="org.springframework.http.converter.StringHttpMessageConverter">
        <property name="supportedMediaTypes">
          <list>
            <value>text/html;charset=UTF-8</value>
          </list>
        </property>
      </bean>
    </mvc:message-converters>
  </mvc:annotation-driven>
  <!-- view 폴더 설정 -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
  </bean>
  
  <!-- 의존성 주입을 위한 클래스 설정(패키지명 포함 클래스 명까지 작성해야 함) -->
  <bean id="userdao" class="dao.UserDao"></bean>
</beans>

mvc-config.xml 설정에서 beans 태그 안에 bean 태그를 넣어서 어떤 클래스를 의존성 주입으로 사용할 건지 작성합니다.

package dao;
 
import java.util.List;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import model.User;

// User 데이터 Dao 클래스, AbstractDao를 상속받고 제네릭은 User 클래스를 설정한다.
public class UserDao extends AbstractDao<User> {
  // 생성자 재정의 protected에서 public으로 바꾸고 파라미터를 재설정한다.
  public UserDao() {
    // protected 생성자 호출
    super(User.class);
  }
 
  // 모든 데이터 취득
  @SuppressWarnings("unchecked")
  public List<User> selectAll() {
    // AbstractDao 추상 클래스의 transaction 함수를 사용한다.
    return super.transaction((em) -> {
      // 쿼리를 만든다. (실무에서는 createQuery가 아닌 createNamedQuery를 사용해서 Entity에서 쿼리를 일괄 관리한다.)
      Query query = em.createQuery("SELECT u FROM User u");
      // 결과 리턴
      return (List<User>) query.getResultList();
    });
  }
 
  // Id에 의한 데이터를 취득한다.
  public User selectById(String id) {
    // AbstractDao 추상 클래스의 transaction 함수를 사용한다.
    return super.transaction((em) -> {
      // 쿼리를 만든다. (실무에서는 createQuery가 아닌 createNamedQuery를 사용해서 Entity에서 쿼리를 일괄 관리한다.)
      Query query = em.createQuery("select u from User u where u.id = :id");
      // 파라미터 설정
      query.setParameter("id", id);
      try {
        // 결과 리턴
        return (User) query.getSingleResult();
      } catch (NoResultException e) {
        // 데이터가 없어서 에러가 발생하면 null를 리턴
        return null;
      }
    });
  }
}

위 클래스는 이전 글에서 작성한 UserDao 클래스입니다.

package controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import dao.UserDao;
import model.User;

@Controller
public class Test {

  // 의존성 주입 설정
  @Autowired
  // mvc-config.xml에서 설정한 bean-id
  @Qualifier("userdao")
  private UserDao userdao;

  // 요청 url 패턴
  @RequestMapping(value = "/index.html")
  public String index(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // userdao로부터 selectById 함수를 통해서 데이터를 취득한다.
    User user = userdao.selectById("nowonbun");
    // view(.jsp)로 데이터를 넘기기 위한 modelmap
    modelmap.addAttribute("Data", user.getName());
    // view의 파일명
    return "index";
  }
}

Controller 소스에서는 맴버 변수에 @Autowired를 설정하여 의존성 주입하는 변수라는 것을 정의하고 @Qualifier를 설정하여 mvc-config의 bean을 매핑시켜서 인스턴스를 생성합니다.

index 함수에서 userdao의 selectById 함수를 이용해서 데이터베이스로부터 데이터를 취득합니다.


데이터를 취득해서 modelmap 변수를 통해서 view로 데이터를 표시합니다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
  </head>
  <body>
    <!-- modelmap으로 넘겨 받는 데이터  -->
    ${Data}
  </body>
</html>

Spring에서 의존성 주입하는 방법은 기본적으로 위의 형태입니다.


그런데 @Autowired의 대해서는 맴버 변수만 사용하는 것이 아니고 함수나 생성자에도 사용이 가능합니다.

package controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import dao.UserDao;
import model.User;

@Controller
public class Test {
  // 요청 url 패턴
  @RequestMapping(value = "/index.html")
  // 파라미터 userdao에 @Autowired와 @Qualifier의 어트리뷰트를 설정해서 의존성 주입을 할 수 있다.
  public String index(@Autowired @Qualifier("userdao") UserDao userdao, ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // userdao로부터 selecteId 함수를 통해서 데이터를 취득한다.
    User user = userdao.selectById("nowonbun");
    // view(.jsp)로 데이터를 넘기기 위한 modelmap
    modelmap.addAttribute("Data", user.getName());
    // view의 파일명
    return "index";
  }
}

위 index 함수에 UserDao 파라미터를 넣어서 앞에 @Autowired @Qualifier("userdao")를 넣어서 사용이 가능합니다.


생성자에도 함수와 마찬가지로 파라미터에 넣어서 의존성 주입을 사용할 수 있습니다.

package controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import dao.UserDao;
import model.User;

@Controller
public class Test {
  // dao의 맴버 변수
  private final UserDao userdao;
  // 생성자 - @Autowired 어트리뷰트를 사용해서 의존성 주입
  @Autowired
  // 파라미터로 mvc-config의 bean-id를 설정
  public Test(@Qualifier("userdao") UserDao userdao) {
    // 맴버 변수 설정
    this.userdao = userdao;
  }
  
  // 요청 url 패턴
  @RequestMapping(value = "/index.html")
  // 파라미터 userdao에 @Autowired와 @Qualifier의 어트리뷰트를 설정해서 의존성 주입을 할 수 있다.
  public String index(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // userdao로부터 selecteId 함수를 통해서 데이터를 취득한다.
    User user = userdao.selectById("nowonbun");
    // view(.jsp)로 데이터를 넘기기 위한 modelmap
    modelmap.addAttribute("Data", user.getName());
    // view의 파일명
    return "index";
  }
}

함수의 변수로 의존성 주입을 받던가 생성자로 의존성 주입을 사용하던가 결과는 같습니다. 어차피 싱글톤 패턴이기 때문에 의존성 주입으로 받는 인스턴스의 데이터는 같습니다.

개인적으로 생성자나 함수 파라미터로 받게 되면 오히려 가독성이 떨어지므로 맴버 변수로 설정하는 게 좋을 것 같습니다.


그리고 @Autowired의 옵션 중에 required가 있습니다. 옵션을 false로 하면 mvc-config에서 bean을 설정하지 않아도 에러가 발생하지 않고, null 값이 입력됩니다.

기본 값은 true로써 설정하지 않으면 에러가 발생합니다.

역시 개인적으로 false로 해서 에러를 넘어서는 것보다 에러를 발생시켜서 버그를 방지하는 편이 좋을 듯 싶네요.


여기까지 Spring 프레임워크에서 JPA 사용법(의존성 주입 @Autowired)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.