프로그래밍/Spring

Spring 2일차 - DI(의존성 주입)

윤도ri 2022. 3. 15. 00:24

 1일차에서 스프링의 개념에 대해 배웠다. 오늘은 어떻게 의존성을 주입하는지 스프링에서는 어떻게 DB를 

연결하고 객체를 만드는지 직접 실습하면서 배워볼 것이다. 

 먼저 의존성 주입 테스트를 해볼건데 레스토랑, 셰프, 호텔 클래스를 만들어서 레스토랑과 호텔 객체에
셰프 객체를 주입하는 시나리오를 고려해보서 만들 것이다. 

 

<목표>
1. 생성자, setter를 이용한 주입으로 의존성 주입 구현
2. 설정 방식은 XML, 어노테이션을 통해서 처리

 

 

 

<예제1- setter,getter로 주입하기>

1. chef 클래스 만들기 

- setter 기능 , toString() 을 자동생성하기 위해 @Data 를 import 해준다.

@Data : Lombok의 setter를 생성하는 기능을 사용하기 위함과 생성자, toString(0등을 자동으로 생성하기 위함. 

 

2. restaurant 클래스 만들기 

 이 클래스에서는 chef 객체를 주입해야한다.  chef에 대한 setter 를 사용해서 주입할 수 있으므로  이 때 @setter를 사용한다. 그리고 뒤에 @Autowired 를 적어주면 주입하게 된다. 간단히 설명하면 restaurant 클래스가 만들어질때 setter를 사용할거고 그 setter는 사용할때 주입을 받아온다라는 의미이다. 

 

3.테스트하기

 1) 주입이 잘 되었는지 테스트하기 위해서 src/text/java 폴더에 test 클래스를 만들어 준다. 

restaurant 에서 한것처럼 restaurant 객체를 사용하려면 똑같이 주입해주어야 한다.

  @setter(onMethod_ = @Autowired)

   private Restaurant restaurant ; 

 

여기서 이상한 점은 existTest()를 호출하려면 sampletest를 객체화 하고 호출하기위한 또 다른 클래스가 필요하다... 

이러면 무한반복에 빠지게 된다. 그래서 스프링에서는 객체를 만들어서 테스트 하는것이 아니라 spring-test 모듈을 이용하여 스프링을 가동시키고 스프링 동작을 활성화한다. 스프링을 실제로 작동시킨다는 보다는 가짜(?)환경에서 작동시킨다고 생각하면 된다. (= SpringJUnit4Class 를 사용한다)

 

 2) 스프링을 동작시키기 위해서 @Runwith 을 사용한다. 또한 특정 클래스를 어떤것으로 실행시킬 지 정해주어야 하므로 위에서 말했던 SpringJUnit4ClassRunner.class 를 괄호안에 적어준다. 

  @Runwith(SpringJUnit4ClassRunner.class)

 

 3) 출력을 위해서 @Log4j 도 사용해준다. 

   @Log4j : syso를 대신에 우리가 사용할 log를 찍어낼 객체이다. 

    exampleTest안에 log.info(restuarant); 적어주기.

 

3) @test

 exampleTest가 실행될 수 있도록 이 메소드 위에 @test를 적어 테스트 대상임을 알린다. 

 

여기 까지 하고 실행을 시켜보자.

Run as > Junit Test   

실행을 해보면 테스트가 되고 테스트 실패(벽돌색) or 성공(초록) 결과를 알려준다.

 

여기까지만 하면 테스트가 실패한다..! 테스트 오류 메세지를 보니  Failed to load ApplicationContext 라고 나온다. 무슨 의미일까? chef나 restaurant 은 주입을 받아서 사용할 것이다. 그래서 스프링에게 내가 주입해서 사용할거니까 너가 관리해 라고 말해주어야 한다.

 

4)@Component  그래서 관리해야하는 chef와 restuarant 클래스가 관리해야하는 대상임을 알려주어야 한다. 각각 클래스 위에 @component 라고 적어주면 된다.  이제 잘 테스트가 될 것 같지만 오산이다. 왜냐하면 물론 우리가 스프링에게 관리해야할 대상임을 알려준 것은 사실이나 이것만 해서는 관리를 해주지 않는다. 간단히 이야기하자면 지금은 관리할 대상에 깃발을 꽂아놓은 것이고 찾을때는 깃발을 보고 찾을 수 있도록 규칙을 설정해주어야 한다. 

 

5) 규칙을 설정해주는 곳은 src/main/webapp/WEB-INF/spring/root-context.xml 에 적어주면 된다. 1일차에서 말한것처럼 applicationText가 관리하는 객체들은  '빈(bean)'이라고 부르고 각각 하나하나 다 관리할 객체들을 <bean></bean>에 적어주면 된다. 그런데 지금 보니 com.koreait.sample 패키지가 테스트 자바 폴더 둘다에 있다. 둘다 적어주려면 코드가 길어지고 불편하다.그러므로 한번에 다 관리해라 ~ 라고 쓰기 위해서 한번에 쓸 수 있는 속성을 추가해주어야 한다. 외울 필요는 없지만 왜 쓰는지는 알고 있어야 한다.

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

 

6) 이제 이 속성으로 인해 한번에 관리할 수 있는 태그들이 만들어졌을 것이다 . com.koreait.sample 을 관리하도록 적어준다. component 찾아서 관리해라고 적어준것이다.  <context:component-scan base-package="com.koreait.sample"/>

 

이제 다 됐다..!! 실행을 다시 시켜보자. what? 다시 실패한다. 왜일까?  잘 생각해보자 테스트를 하려면 스프링이 실행이 되야지만 주입을 할 수 있을것이다. root-context.xml에 우리가 com.koreait.sample에 가서 component 를 찾아서 주입하라고 규칙을 설정해두었으므로,,  

다시 말하자면, 주입을 하기 위해서는 SampleTest에서 root-context.xml을 불러와서 같이 사용을 해서 테스트를 해야한다. (= 주입을 하지 않는다면 불러올 필요 X)

7)SampleTest.java에서 root-context.xml 불러오기.

 

@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")

 

 주입 성공!! restaurant 에 값이 들어있음을 알 수 있다.

 

 

 

 * 추가적으로 테스트를 할때 null 값인지 확인해 볼 수도 있다. 

   assertNotNull(); 

 * chef도 잘 주입되었는지 확인할 수 있다.

   log.info(restaurant.getChef()); // Chef()  ---chef에서 @Data를 사용했으므로 toString이 재정의되어 이와같이 출력된다.

 


<예제2 - 생성자로 주입하기> 

1.Hotel 클래스 만들기

 1)스프링이 관리해야하니까 @component 적어주고 tostring재정의 getter 사용 위해서 @ToString, @Getter를 적어준다. 

 2) 생성자를 통해서 주입을 하기 위해서는 기본 생성자가 아닌 다른것이 필요하다. 

  @AllArgsConstructor  (인스턴스 변수로 선언된 모든 것을 파라미터로 받아오는 생성자를 작성한다.

 * static 변수는 받아오지 않는다. 

 

객체를 만들때 주입이 안되는 객체도 함께 받아오는 경우도 있다. (= @component 깃발 안꽂았다)

전혀 선언이 안되어있으면 스프링이 주입을 못해준다. 그래서 생성자가 생성이 되지 않는다. 

그래서 이럴땐 주입이 필요한 것들만 주입을 해주는 생성자가 필요하다. 이 때 그 생성자는 

@RequiredArgsConstructor 라고 하며 특정 요구 변수에 대해서만 받아오는 생성자를 작성할 때 사용한다. 

특정 요구 변수인 @NonNull 이나 final이 붙은 인스턴스 변수에 대한 생성자를 만들어 낸다. 

 

2.test 클래스 만들기 (HotelTest.java) 

 1)Spring 동작시키기 

   @Runwith

 2)동작할때 주입해야하므로 규칙적어놓은 위치 적어둔다

   @ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")

 3)@Log4j 임포트하기 

 4)hotel 객체 주입하기 

  @setter(onMethod_ = @Autowired)

 5)test 메소드 만들고 Test 어노테이션 적기 

  public void existTest(){

       assertNotNull(hotel);

       log.info(hotel.getChef());

  }

  * test 메소드는 public void만 가능하다.

 


<위의 예제 동작시 생기는 일>

1. 스프링 프레임워크가 시작되면서 먼저 스프링이 사용하는 메모리 영역을 만든다.
2. 스프링 내부적으로 ApplicationContext라는 이름의 객체가 만들어진다.
3.스프링은 자신이 생성하고 관리해야 하는 객체들에 대한 설정이 필요하고 이 설정파일이 바로 root-context.xml 이다.
root-context.xml에 설정되어 있는 <context:component-scan> 태그의 내용을 통해서 com.koreait.sample 패키지를 스캔하기 시작한다.
4. 해당 패키지에 있는 클래스들 중에서 스프링이 사용하는 @Component라는 어노테이션이 존재하는 클래스의 인스턴스를 생성한다.
5. Restaurant객체는 Chef 객체가 필요하다는 어노테이션(@Autowired) 설정이 있으므로, Chef 객체의 레퍼런스를 restaurant 객체에 주입한다. Hotel 객체는 @RequiredArgsConstructor 어노테이션을 통해 생성될 때 내부에 있는 필수 객체들을 주입받아 온다.

 


<JDBC 연결 테스트하기> 

1. JDBCTest 클래스 만들기 

 JDBC 드라이브 불러오고 연결하기 위해서 만들어 놓는다. 원래는 DBConnection 클래스를 만들고 

getConnection() 메소드를 사용했었다. 근데 이번엔 그 대신에 static 블럭을 사용해서 static영역에 코드작성을 해주게 된다.  

2. 밑에 test로 연결되는 지 확인하는 메소드 적기 

  connection이 잘 만들어졌는지만 확인해보면 연결이 잘되어있는지 확인 할 수 있다. 

 Connection conn = DriverManager.getConnection()

 

3.try catch문 사용해서 연결안된경우 catch로 잡아준다.

try(statement) : 소괄호안에 close를 필요로 하는 인스턴스를 작성하면 자동으로 close를 호출해준다.원래는 한번 사용할때마다 close로 닫아줘야 하지만 try뒤에 적어주면 자동으로 close해준다.

 

 try(      connection conn = DriverManager.getConnection(           "jdbc:oracle:thin:@localhost:****:XE", "id","password")){   log.info(conn);       //null이 아닌게 나오면 정상적으로 연결됐음을 의미한다.}

 

따로 이용하는게 없어서 스프링도 작동안시켰고 root-context도 굳이 알려줄 필요가 없다. 

 

 


<DBCP 연결 테스트 하기> 1.root-context.xml에 db연결하는 설정 적어주기   이때 우리는 히카리 라는 것을 사용한다. DBCP (수영장 풀)을 관리해주는 중간 관리자 같은 느낌이다..수영장이 고급화된 느낌이다. 

 

  <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">

     <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>

     <property name="username" value="id"/>

     <property name="password" value="password"/>

  </bean>

 

 

2.설정만 적어놓은 것이므로 사용하려면 dataSource객체도 적어주어야 한다. //constructor-arg : 생성자의 매개변수. 데이터소스를 생성하는것은 스프링이 해주지만 그때 필요한 정보들이있고 어디있는지 만들수 있기때문에  reference 적어주어야 한다.

 

  <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" > 

   <constructor-arg ref="hikariConfig"></constructor-arg>

  </bean>

 

3.DataSourceTest.java 만들기 (com.koreait.persistence 패키지) 1) 스프링이 작동해야하므로  @Runwith 2) root-context.xml 불러오기@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml") 3) @Log4j 임포트하기  4) dataSource 변수 만들기     private Dataource datasource;  5) SETTER 이용하여 주입하기    @Setter(OnMethod_ = @Autowired) 6) 잘만들어졌는지 확인 위해 메소드 만들기 (+ @test)      public void connectionTest(){

       try (

           Connection conn =  datasource.getConnection();

    ){

          log.info(conn); //null아니면 정상출력!

      }

      catch(Exception e){

     }

    } 


<mybatis 연결 테스트하기> 

 

1.root-context.xml 에 설정들 적어주기 

 DBCP오 마찬가지로 마이바티스에서도 username password 등 property 필요 근데 mybatis의 경우 datasource에 만들어져있음. 그러므로 이 객체를 참고하도록 만들어 주면 된다.

 
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>

 

2.변수 선언, 메소드 만들고 @setter이용하여 주입하기 

@setter(OnMethod_ = @Autowired) 

private SqlsessionFactory factory; 

 

@Test
public void mybatisTest() {
try(
SqlSession sqlsession = factory.openSession(true);
Connection conn = sqlsession.getConnection()
) {
log.info(conn);
}
catch(Exception e) {
fail(e.getMessage());    //junit에 있는 메소드 하나만 추가함.
}

}


<예제 time data 만들고 테스트하기>

1. 인터페이스로 TimeMapper 만들기 

 

 작성한 쿼리문이 자동으로 수행된다. 결국 mybatis는 DBCP를 쓰고 jdbc 기반으로 만들어진것이다. 다시말해서 내부의 기반은 JDBC 이다. 이 쿼리문을 수행하기 위한 JDBC 코드가 수행되어야 한다. 예를 들어 mybatis로 xml에 db를 연결해줄때 Board.insertBoard 이런식으로 사용하게 된다. 여기서 nameSpace가 대문자인 이유는 mapper를 적어주었을때 실제로 Board라는 클래스에서 insertBoard라는 메소드를 호출해서 수행되는 방식으로 insert 가 수행되는 것이다.

 

 

//mapper.insertBoard();
//sqlsession.insert("Board.insertBoard")

<mapper namespace="Board">
<insert id="insertBoard">
insert into t_board values(1,"테스트")
</insert>
</mapper>
class Board{
public int insertBoard(){
40줄
}
}
*/

 

최소한의 코드가 아닌 경우에는 xml에 따로 적어 주어야 한다. 

 

1-2. TimeMapper.xml 생성하기 

원래는 아래 sqlsession.selectOne을 사용하여 호출하여 사용해야하 한다.

2. TimeMapper.java 에서 호출하기(=연결하기)

   public String getTime2(); 

 

메소드를 적어주면  이미 com.koreait.mapper.TimeMapper 라는 클래스가 만들어져있고 안에 쿼리문이 구현되어 있으므로 sqlsession.selectOne(~)을 적지 않더라도 바로 호출할 수 있다.

 

근데 mapper를 사용하려면 mapper인터페이스 타입의 객체도 만들어야 하고 mybatis타입 객체이므로 xml 이 어디있는지 알려줘야 한다. @component 했던것 처럼,,

 

3.root-context에 xml위치  적어주기 

<mybatis-spring:scan base-package="com.koreait.mapper"/>

 

4.MapperTest.java 만들기

getClass().getName() = 클래스 이름&nbsp;