2020.03.06 수정


Mybatis 버전의 방명록 애플리케이션( 링크 )을 JPA 버전으로 만드는 주제입니다.





1. 환경 설정




1) pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>guestbook</groupId>
<artifactId>guestbook</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>guestbook</name>

<properties>
<org.springframework-version> 4.1.2.RELEASE </org.springframework-version>
</properties>


<dependencies>
<!-- Spring Application Context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
</dependency>

<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework-version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>

<!-- JSTL -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

<!-- 스프링 ORM -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework-version}</version>
</dependency>


<!-- JPA, Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.0.11.Final</version>
</dependency>

<!-- Common DBCP(Database Connection Pool) -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>

<!-- MySQL JDBC Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<release>9</release>
</configuration>
</plugin>

<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<warSourceDirectory>webapp</warSourceDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>
  • JPA를 웹에서 사용하기 위해, spring-orm 의존성을 추가합니다.
  • 그리고 JPA 구현체인 Hibernate를 사용하기 위해 hibernate-entitymanger 의존성도 추가합니다.
  • MySQL JDBC Driver를 사용하는 이유는 JPA도 JDBC를 사용하기 때문입니다.
    • 개발자가 SQL을 작성하지 않을 뿐이지, Hibernate 내부적으로 JDBC API가 수행됩니다.



2) WEB - INF / web.xml
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID"
version="3.1">

<display-name>guestbook</display-name>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>

<!-- Context parameter ( 전역 파라미터 ) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<!-- Context Loader Listner -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- DispatcherServlet -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

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

<!-- Encoding Filter -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>

<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>


<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

</web-app>

특별한 것 없는 스프링 설정이며, JPA와 관련된 설정은 없습니다.





3) WEB - INF / spring-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" 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/aop http://www.springframework.org/schema/aop/spring-aop.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"
xmlns:mvc="http://www.springframework.org/schema/mvc">
<mvc:annotation-driven />

<!-- 서블릿 컨테이너의 디폴트 서블릿 위임 핸들러 -->
<mvc:default-servlet-handler />

<!-- @Controller 스캔 -->
<context:annotation-config />
<context:component-scan base-package="com.victolee.guestbook.controller" />

<!-- 뷰 리졸버 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
<property name="order" value="1" />
</bean>
</beans>

마찬가지로 JPA와 관련된 설정은 없습니다.





4) WEB - INF / applicationContext.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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.1.xsd
http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring
http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd">


<context:annotation-config />

<!-- 트랜잭션 관리자 활성화 -->
<tx:annotation-driven/>

<!-- 트랜잭션 관리자 등록 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!-- JPA 예외를 스프링 예외로 변환 -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

<!-- JPA 설정 ( 엔티티 매니저 팩토리 등록 ) -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />

<!-- @Entity 탐색 범위 -->
<property name="packagesToScan" value="com.victolee.guestbook.domain" />

<!-- 하이버네이트 구현체 사용 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>

<!-- 하이버네이트 상세 설정 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <!-- 방언 -->
<prop key="hibernate.show_sql">true</prop> <!-- SQL 보기 -->
<prop key="hibernate.format_sql">true</prop> <!-- SQL 정렬해서 보기 -->
<prop key="hibernate.use_sql_comments">true</prop> <!-- SQL 주석 보기 -->
<prop key="hibernate.id.new_generator_mappings">true</prop> <!-- JPA 표준에 맞게 새로운 키 생성 전략을 사용-->
<prop key="hibernate.hbm2ddl.auto">create</prop> <!-- DDL 자동 생성 -->
</props>
</property>
</bean>

<!-- Connection Pool DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/jpadb" />
<property name="username" value="jpadb" />
<property name="password" value="jpadb" />
</bean>

<context:component-scan base-package="com.victolee.guestbook.service, com.victolee.guestbook.repository">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Repository" />
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Service" />
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Component" />
</context:component-scan>

</beans>

JPA와 관련된 설정 부분이 드디어 나왔네요.

하나 하나씩 설정을 살펴보면 다음과 같습니다.


<!-- 트랜잭션 관리자 활성화 -->
<tx:annotation-driven/>

<!-- 트랜잭션 관리자 등록 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
  • 두 설정은 JPA에서 제공하는 @Transactional을 사용하기 위함입니다.
    • JPA는 기본적으로 Transaction( 트랜잭션 )을 지원하기 때문에 위의 설정이 필요합니다.
    • 데이터 INSERT / UPDATE / DELETE 작업을 위해 @Transactional 어노테이션은 필요합니다.



<!-- JPA 예외를 스프링 예외로 변환 -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
  • 주석 그대로 JPA에서 발생하는 예외를 스프링 예외로 처리하는 설정입니다.


<!-- JPA 설정 ( 엔티티 매니저 팩토리 등록 ) -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />

<!-- @Entity 탐색 범위 -->
<property name="packagesToScan" value="com.victolee.guestbook.domain" />

<!-- 하이버네이트 구현체 사용 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>

<!-- 하이버네이트 상세 설정 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <!-- 방언 -->
<prop key="hibernate.show_sql">true</prop> <!-- SQL 보기 -->
<prop key="hibernate.format_sql">true</prop> <!-- SQL 정렬해서 보기 -->
<prop key="hibernate.use_sql_comments">true</prop> <!-- SQL 주석 보기 -->
<prop key="hibernate.id.new_generator_mappings">true</prop> <!-- JPA 표준에 맞게 새로운 키 생성 전략을 사용-->
<prop key="hibernate.hbm2ddl.auto">create</prop> <!-- DDL 자동 생성 -->
</props>
</property>
</bean>

EntityManagerFactory를 bean으로 등록합니다.


테이블로 매핑 시킬 객체를 엔티티( Entity )라고 하는데, 이 엔티티를 관리하는 것이 엔티티 매니저( Entity manger )입니다.

그리고 엔티티 매니저를 만드는 곳이 엔티티 매니저 팩토리 ( Entity Manager Factory )입니다.

나중에 언급될 내용이므로 지금은 일단 이렇게만 이해 하시면 됩니다.


  • <!-- @Entity 탐색 범위 -->
    • 해당 클래스가 엔티티임을 나타내는 @Entity 어노테이션을 스캔하기 위한 범위를 작성합니다.
  • <!-- 하이버네이트 구현체 사용 -->
    • JPA를 구현한 여러 구현체 중에서 Hibernate를 사용하겠다고 설정합니다.
  • <!-- 하이버네이트 상세 설정 -->
    • Hibernate 설정을 작성합니다.
    • 꼭 필요한 설정은 아니지만, Hibernate가 출력하는 쿼리 및 개발의 편의성을 제공해주기 때문에 위와 같이 작성합니다.
    • <prop key="hibernate.hbm2ddl.auto">create</prop>
      • 마지막으로 이 옵션은 애플리케이션이 실행되면 기존의 테이블을 삭제하고, 새로운 테이블을 생성한다는 의미입니다.
      • 이 옵션은 위험한 옵션이므로 사용하지 않는 것이 좋지만, 앞으로 살펴볼 예제에서는 편의성을 위해 사용할 것입니다.
      • 설정 할 수 있는 값은 다음과 같습니다.
        • none
          • 기본 값이며 아무 일도 일어나지 않습니다.
        • create-only
          • 데이터베이스를 새로 생성합니다.
        • drop
          • 데이터베이스를 drop 합니다.
        • create
          • 데이터베이스를 drop 한 후, 데이터베이스를 새로 생성합니다. ( drop + create-only을 수행한 것과 같습니다. )
        • create-drop
          • SessionFactory가 시작될 때 스키마를 drop하고 재생성하며, SessionFactory가 종료될 때도 스키마를 drop 합니다.
        • validate
          • 데이터베이스 스키마를 검증 합니다.
        • update
          • 데이터베이스 스키마를 갱신 합니다.




2. 데이터베이스 유저 생성

applicationContext.xml 파일에서 dataSource를 다음과 같이 정의했습니다.

<!-- Connection Pool DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/jpadb" />
<property name="username" value="jpadb" />
<property name="password" value="jpadb" />
</bean>


따라서 DB 커넥션이 잘 맺어지려면, jpadb 이름의 database를 생성하여 jpadb 유저에게 모든 권한을 주도록 해야 합니다.

CMD 창을 열고 이 작업을 진행하겠습니다. ( system에 mysql이 설치되어 있어야 합니다. )

// my sql 접속

# mysql -u root -p


// jpadb 데이터베이스 생성

# create database jpadb;


// 유저 생성

# create user 'jpadb'@'localhost' identified by 'jpadb';


// 유저에게 jpadb 데이터베이스의 모든 권한 부여

# grant all privileges on jpadb.* to 'jpadb'@'localhost' identified by 'jpadb';

exit


// jpdb 유저로 접속

# mysql -u jpadb -p


// jpadb 데이터베이스가 있는지 확인

# show databases;



설정이 끝났습니다.

톰캣 서버를 실행했을 때 오류가 발생하지 않으면 정상적으로 설정이 된 것입니다.



*** error ***

Caused byjava.lang.ClassNotFoundExceptionjavax.xml.bind.JAXBException

만약 위와 같은 오류가 발생한다면 pom.xml 파일에서 아래의 의존성을 추가하면 됩니다.

( JDK 1.9를 사용했을 경우 발생하는 문제인 것 같습니다. - 참고 )

<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>




이상으로 환경 설정을 마쳤습니다.

다음 글에서는 객체를 테이블로 매핑하는 엔티티 매핑에 대해 알아보겠습니다.