인턴을 했을 때 JPA 를 쓰긴 썼었는데 안 쓴거나 마찬가지였던 것 같다ㅠㅠㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

이전에는 아래와 같이 객체를 테이블에 맞춰서 모델링 했었음...

class Job{
    @Id
    private String jobId
    ...
}
class Step{
    @Id
    private String stepId;
    private String jobId;
    ...
}

이렇게 했을 때는 객체를 테이블에 저장하거나 조회할 때는 쉽고 편하다.

하지만 job에서 step을 조회할때, 또는 step에서 job을 조회하는 경우 참조된 객체를 가져올 수 없다.

물론 이 때는 데이터베이스에 저장이 주 기능이어서 딱히 상관없었을 수도 있다,,,

 

이번 토이 프로젝트에서는 JPA를 이용하여 객체지향 모델링을 해 볼건데

이번에는 객체 참조를 통해 관계를 맺도록 해볼것이다.

 

  • build.gradle dependency 추가

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

server:
  address: localhost
  port: 8080

spring:
  jpa:
    show-sql: true # API 호출시, SQL 문을 콘솔에 출력
    generate-ddl: true # DDL 정의시 데이터베이스의 고유 기능을 사용 ex) 테이블 생성, 삭제 등
    database: mysql
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
  datasource:
    url: jdbc:mysql://localhost:3306/ewhaianDB?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 
    driver-class-name: com.mysql.cj.jdbc.Driver

 

  • Entity class 생성,,,을 하기 전에 저번에 정의했던 연관관계를 다시 보면 (객체는 참조가 있는 방향으로만 조회가 가능하다.)

    • 회원-글/시간표/강평/댓글: 일대 다 관계

    • 글-댓글: 일대 다 관계

    • 글/댓글-사진: 일대 다 관계

    • 강의-강의평가: 일대 다 관계

    • 시간표-강의:다대 다 관계

      • 시간표에 다른 강의 여러개 넣을 수 있고

      • 같은 강의가 여러 시간표에 들어갈 수 있다

      • --> 다대 다 관계는 관계형 데이터베이스 뿐만 아니라 엔티티에서도 거의 사용하지 않는다고 함

      • --> 따라서 연결 엔티티 강의시간표를 추가해서 일대다 관계+다대일 관계로 만들도록 한다.

      • 강의 시간표에는 담은 강의의 학점 합도 포함하도록 한다.

    • 추가) 즐겨찾기의 경우도 다대다 관계이므로 관계를 쪼개도록 한다.

 

 

 

 

 

 


  • JPA를 사용할 때 양방향 관계에서 주의할 점

@Entity
@Getter @Setter
public class BookStore{
    @Id @GeneratedValue
    private Integer id;
    
    private String name;
    
    @OneToMany(mappedBy="bookStore")
    private Set<Book> books=new HashSet<>();
    
    void add(Book book){
        //book.setBookStore(this); 이 부분을 꼭 설정해주도록
        this.books.add(book);
    }
}
@Entity
@Getter @Setter
public class Book{
    @Id @GeneratedValue
    private Integer id;
    private Sring isbn;
    private String title;
    
    @ManyToOne
    private BookStore bookStore;
}

위와 같이 객체를 정의해두고 테스트 코드를 아래와 같이 돌렸을 때 결과를 확인하면

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoJpaTestApplicationTests{
    @Autowired
    BookStoreRepository bookStoreRepository
    
    @Autowired
    BookRepository bookRepository
    
    @Test
    public void contextLoads(){
        BookStore bookStore=new BookStore();
        bookStore.setName("시애틀 책방");
        bookStoreRepository.save(bookStore);
        
        Book book=new Book();
        book.setTitle("JPA 공부 좀 하면서 쓰세요.");
        bookStore.add(book);
        bookRepository.save(book);
    }
}

--> book DB를 확인하면 bookStore id가 없다. (연관 관계 맵핑이 안되어 있다.)

 

mappedBy가 없을 때에는 Book과 BookStore은 양방향 관계가 아니고 서로 다른 단방향 관계 2개이다.

BookStore에서 Book을 참조하는 OneToMany 관계는 기본적으로 join 테이블(book_store_id - book_id)이 생성된다.

 

여기서 mappedBy="BookStore"를 써주면 양방향 관계가 된다. (OneToMany와 ManyToOne을 하나로 묶어준다.)

--> 이 때 관계의 주인을 Book(Many에 해당하는 쪽)으로 설정한다.

이 경우에는 join테이블이 생기는 것이 아니라 Book과 BookStore 테이블 2개만 생기고,

foreign key를 Book쪽에서 가지고 있게 된다. (Book 테이블에 book_store_id가 있음)

 

즉, mappedBy는 관계의 주인을 알려준다.

= 관계의 주인이 Book이고, Book에서 자기 자신을 BookStore라고 참조하고 있다는 뜻

= 이 경우 관계의 주인인 쪽에 관계가 설정 되어야 데이터베이스에 반영된다.

= add를 할 때 관계의 주인인 Book에 관계를 설정해야 sync가 된다.

 

출처

https://www.youtube.com/watch?v=brE0tYOV9jQ

https://victorydntmd.tistory.com/208

https://velog.io/@conatuseus/%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91-%EA%B8%B0%EC%B4%88-1-i3k0xuve9i

 

'Java (+ Spring)' 카테고리의 다른 글

Java Unit Test: Junit5  (0) 2021.02.10
Spring Boot: Spring Security & 카카오 로그인 API(...ing)  (0) 2020.10.06
Spring: Spring MVC 설정 하기  (0) 2020.07.26
Android: tutorial page  (0) 2020.07.18
Android: Launch screen  (0) 2020.07.16

Spring MVC 프레임워크 Support 추가 후 /web 패키지가 생성되는데, 이 아래에 있는 web.xml 파일의 아래 부분을

  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>*.form</url-pattern>
  </servlet-mapping>

 

이렇게 바꿔줘야 한다.

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

 

톰캣의 web.xml파일은 톰캣의 실행환경에 대한 정보를 담당하는 환경설정 파일이다. 

각종 servlet의 설정과 servlet 매핑, 필터, 인코딩 등을 담당한다.
web.xml은 각 application이 deploy될 때 각 application의 'WEB-INF/web.xml' deployment descripter에 따라서 처리가 된다.


 

일단 서블릿과 웹 페이지 동작에 대해 잘 모르겠어서 관련 내용을 찾아 보았다.

 

서블릿이란 웹 프로그래밍에서 클라이언트의 요청을 처리하고 그 결과를 다시 클라이언트에게 전송하는 서블릿 클래스의 구현 규칙을 지킨 자바 프로그래밍 기술이다. 즉, 클라이언트의 HTTP 요청에 대해 특정 기능을 수행, HTML문서를 생성등의 응답을 하는 인터넷 서버 프로그램이다. 일반적으로 웹 서버는 정적인 페이지만을 제공한다. 따라서 동적인 페이지를 제공하기 위해서 웹 서버는 다른 곳에 도움을 요청하여 동적이 페이지를 작성해야 한다. 동적인 페이지로는 임의의 이미지만을 보여주는 페이지와 같이 사용자가 요청한 시점에 페이지를 생성해서 전달해 주는 것을 의미한다.

 

여기서 웹 서버가 동적인 페이지를 제공할 수 있도록 도와주는 어플리케이션이 서블릿이고, 동적인 페이지를 생성하는 어플리케이션이 Common Gateway Interface이다. CGI는 별도로 만들어 놓은 프로그램에 HTML의 GET/POST 방법으로 클라이언트의 데이터를 환경 변수로 전달하고, 프로그램의 표준 출력 결과를 클라이언트에게 전송한다.

 

서버와 클라이언트는 HTTP request/response로 통신하는데, 서버가 요청을 받으면 정적 자원에 대한 요청일 경우 자원을 반환해주고, 그렇지 않는 경우 CGI 프로그램을 실행하여 해당 결과를 리턴해준다. 이 때, 서버는 CGI 프로그램에게 클라이언트의 요청과 매개 변수를 전달해주고, 결과를 전달 받기 위한 파이프라인을 연결한다. 그래서 CGI 프로그램은 입력에 대한 서비스를 수행하고, 결과를 클라이언트에게 전달하기 위해 결과 페이지에 해당하는 MIME 타입의 컨텐츠 데이터를 웹 서버와 연결된 파이프라인에 출력하여 서버에 전달한다. 서버는 파이프라인을 통해 CGI 프로그램에서 출력한 결과 페이지의 데이터를 읽어 HTTP 응답헤더를 생성하여 데이터를 함께 반환해준다.

 

--> Servlet

  • 클라이언트의 요청에  대해 동적으로 작동하는 웹 어플리케이션 컴포넌트이다.
  • html을 사용하여 요청에 응답한다.
  • 자바 스레드를 이용하여 동작한다.
  • MVC 패턴에서 컨트롤러로 이용된다.
  • HTTP 프로토콜 서비스를 지원하는 javax.servlet.http.HttpServlet 클래스를 상속받는다.
  • UDP보다 속도가 느리다.
  • HTML 변경 시 Servlet을 재컴파일 해야 하는 단점이 있다.

 

이 서블릿을 관리해주는 서블릿 컨테이너가 있다. 서블릿 컨테이너는 서블릿의 생명주기를 관리하고 요청에 따른 스레드를 생성해준다. 또, 클라이언트의 request를 받아주고 response를 보낼 수 있게 웹 서버와 소켓을 만들어 통신을 해준다. 서블릿 컨테이너의 예가 톰캣이다. 톰캣은 실제로 웹 서버와 통신하여 Java Server Page(JSP)와 Servlet이 작동하는 환경을 제공해준다.

 

--> Servlet Container이란

  • 웹 서버와의 통신 지원: 일반적으로 소켓을 만들어서 listen, accept를 함으로써 통신을 하지만, 서블릿 컨테이너는 서블릿과 웹 서버가 쉽게 통신할 수 있도록 API를 제공해준다. 
  • 서블릿 생명주기 관리: 서블릿 컨테이너는 서블릿 클래스를 로딩하여 인스턴스화 하고, 초기화 메소드를 호출하고, 요청이 들어오면 적절한 서블릿 메소드를 호출한다. 또한, 서블릿이 생명이 다 한 순간에는 적절하게 가비지 컬렉션을 진행한다.
  • 멀티 스레드 지원 및 관리: 서블릿 컨테이너는 요청이 들어올 때마다 새로운 자바 스레드를 하나 생성하는데, HTTP 메소드 실행 후에 스레드는 죽는다. 원래는 스레드를 관리해야 하지만 서버가 다중 스레드를 생성 및 운영해주기 때문에 스레드의 안정성에 대해서 걱정하지 않아도 된다.
  • 보안 관리: 서블릿 컨테이너를 사용하면 보안 관련 내용을 서블릿 또는 자바 클래스에 구현하지 않아도 된다. 일반적으로 보안 관리는 xml 서술자에 기록하기 때문에, 보안에  대해 수정할 일이 생기더라도 자바 소스 코드를 수정하여 다시 컴파일 하지 않아도 보안 관리가 가능하다. 

 


 

다시 web.xml 설정으로 돌아가서

톰캣의 ${TOMCAT_HOME}/conf/web.xml 파일에는 3개의 서블릿 매핑이 존재한다.

 

즉 *.jsp, *.jspx와 같은 url 패턴은 JspServlet이 처리하고,

DefaultServlet은 spring Controller mapping과 jsp 패턴에 걸리지 않는 요청 들을 처리한다.

(png, jpg, js, html등 정적인 content)

 

<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

...

<servlet-mapping>
	<servlet-name>jsp</servlet-name>
	<url-pattern>*.jsp</url-pattern>
</servlet-mapping>

...

<servlet-mapping>
	<servlet-name>jsp</servlet-name>
	<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

처음에 servlet-mapping을 /로 설정하면서 default 서블릿을 dispatcher 서블릿으로 덮어쓴 것이다.

그런데 이렇게만 해준다면 기존의 jsp 패턴에 걸리지 않던 png, jpg, js, html과 같은 정적인 content를 요청할 때 오류가 날 것이다.

 

.jsp든 .js, .css든 WAS만 구동하는경우 해당 파일에 직접접근하는 경우는 없다. 일단 요청은 톰캣이 모두 받게된다.

그리고 톰캣은 서블릿에 패턴을 통해서 요청을 처리하게되는데 /로 지정하게되면 *.jsp로 오는 요청은 jsp 서블릿이 처리하겠지만,

그 외에는 default 서블릿이 처리를 해야하는데 이 default 설정을 덮어써서 default 서블릿이 작동하지 않으면 문제가 발생한다.

 

이를 해결하기 위해서는 서블릿 필터 등을 통해 정적인 요청은 default 서블릿으로 명시적으로 위임하도록 하는 2가지 방법이 있다.

이 핸들러를 구성하면 dispatcher가 모든 요청을 default servlet으로 전달한다.

 

WebConfig.java

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
public class MvcConfiguration implements WebMvcConfigurer {

  @Bean
  public ViewResolver getViewResolver(){
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/");
    resolver.setSuffix(".html");
    return resolver;
  }

  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }
}

 

servlet-context.xml: Default ServletHandler가 Bean으로 등록되며 동작

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  <mvc:annotation-driven/>
  <mvc:default-servlet-handler/>
</beans>

 

마지막으로 project structure - articats에 들어가서 필요한 라이브러리를 추가해주도록 한다.

- Spring_5_2_3_RELEASE

- Spring_MVC_5_2_3_RELEASE

 

참고

https://lng1982.tistory.com/97

https://multifrontgarden.tistory.com/145

 

+ Recent posts