이전 글중 eclipse 스프링 레거시 프로젝트 생성과 이어지는 내용이다.
https://riverblue.tistory.com/35
eclipse 스프링 레거시 프로젝트 생성 및 나의 기본 세팅
스프링 레거시로 만들고 싶은게 생겨서 새로 프로젝트를 만든다. ide는 eclipse 2020-09 버전 당연히 market으로 스프링이 설치되어있고 perspective는 spring으로 선택되어있어야 한다. 프로젝트 생성 File >
riverblue.tistory.com
초기 프로젝트 구성으로 나에게 익숙한 Mybatis를 쓰려고 했지만 JPA를 써보기로 했다. 이유는 궁금해서
나는 기존에 ORM에 대해서 잘못알고 있었다. jdbc나 mybatis, jpa처럼 애플리케이션에서 디비에 접근할수 있게 하는 라이브러리는 모두 orm인줄 알았는데 그게 아니었다.
자세한 내용은 길기 때문에 따로 정리를 해야하지만 mybatis와 비교하면 느낌을 좀 알 수 있다.
ORM과 SQLMAPPER
검색을 꽤 해본결과 많은사람들이 Mybatis는 SqlMapper, JPA는 ORM으로 나눠서 정의하고있다. 간단하게 정리하자면,
ORM은 Object Relational Mapping이라는 이름에서 추측하자면 객체와 관계형 매핑이다. https://www.altexsoft.com/blog/object-relational-mapping/를 참고해 이해를 해보면 ORM은 객체와 db의 데이터 사이의 관계를 정의한 Map을 생성하여 동작하기 때문에 Object Relational Mapping이라고 생각한다.
SqlMapper는 SQL문을 통해 객체와 데이터를 매핑하는 것이다. Mybatis의 경우 mapper.xml에서 직접 sql문을 작성하기 때문에 Mybatis는 SqlMapper라고 하고,
JPA는 sql문이 아닌 구현되어있는 인터페이스의 메서드를 통해 데이터를 매핑하기 때문에 ORM이라고 한다.
ORM과 SqlMapper를 비교하는 글들을 보면 orm은 sql문이 아닌 메서드를 통해 매핑한다고 되어있는데, 이러한 정의가 문서로 정의되어있는지 나는 아직 오리지널 출처를 찾아보지 못했기 때문에 개인적으로는 완벽히 믿지않는다.
ORM의 개념으로만 보면 SqlMapper를 포함하는 개념이라고 생각하기 때문이다. 아무튼 많은 사람들이 이런식으로 정의한다고만 알고있으면 될 것 같다.
JPA에 대해서 간단히
Java Persistence Api 이름처럼 자바 영속성에 관한 api로 orm에 대한 인터페이스라고 생각하면된다. 사실 JPA는 표준 명세를 말하는것이며 RDS데이터를 매핑시킬 객체를 Entity라고 하는데, JPA의 핵심은 이 Enity를 관리하는 EntityManager라고 한다. 그러므로 실제로 JPA를 사용하기위해선 Hibernate와 같이 JPA의 EntityManager를 구현한(Hibernate, DataNucleus, EclipseLink 등) 구현체를 사용해야한다(실제로는 더 많은 것을 구현했을 것이다).
내가 사용할 spring-data-jpa는 이 jpa+hibernate의 조합을 더 편하게 사용할수 있도록 한단계 더 추상화 된 모듈이다.
때문에 spring-data-jpa를 활용하면 EntityManager가 아닌 Repository를 구현해 jpa 사용하게 된다.
Mybatis의 Mapper라고 생각하면 편하다.
JPA 설정
스프링 부트에 대한 jpa는 많았지만 레거시에 설정하는건 자료가 많이 부족해서 최소한의 설정으로만 구성했다. 추후에 설정이 바뀌거나 추가될 예정이고, 초보 스멜이 풀풀 날 수있다
이번 글에서는 간단히 CRUD도 아니고 그냥 데이터만 select 되는지, 연결이 되는부분 까지만 이다.
1. Project Facets JPA 추가
먼저 이전에 만들었던 프로젝트를 JPA 프로젝트로 바꿔야한다.
프로젝트 우클릭 > Properties > Project Facets > JPA체크 후 Apply
처음에는 어떤 경고문구가 있었던거 같은데 기억이 안나지만, 아무 설정 없는채로 일단 만들면 적용이 되었던 것 같다.
Convert가 완료 되면 src/main/java에 META-INF가 생기고 그 폴더 안에 persistence.xml이 생겨있다.
src/main/java 구조
간단하게 src/main/java 아래 구성을 훑어보면
controller, service, repository, model 패키지로 나눠져있다.
controller와 service는 Mybatis를 사용할때와 동일하다.
repository는 Mybatis의 Mapper라고 생각하면된다.
model은 VO, DTO, domain들을 모아놓는 패키지로 MVC M이다. 개인적으로 이게 편하다.
2. pom.xml
<!-- JPA -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.1.11.Final</version>
</dependency>
</dependencies>
dependencies에 기존에 있던 mybatis는 삭제하고 jpa를 위한 라이브러리 3개를 추가해주었다.
spring-data-jpa는 우리가 사용할 jpa이기 때문에 당연히 필요하고 나머지 둘은 설정을 위해 필요하다.
3. persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="jobcall">
<class>com.poozim.jobcall.model.Work</class>
<properties>
<!-- <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.c3p0.min_size" value="5"/>
<property name="hibernate.c3p0.max_size" value="20"/>
<property name="hibernate.c3p0.timeout" value="500"/>
<property name="hibernate.c3p0.idle_test_period" value="2000"/>
</properties>
</persistence-unit>
</persistence>
<class>태그는 JPA의 Entity가 될 위에 언급된 model 패키지 안의 객체 클래스들을 선언하면된다. 나는 테스트를위해 일단 Work클래스에만 Entity를 지정했다.
주석처리된 property가 있다.
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
hibernate.dialect 경우 사용하는 DB 종류를 지정하는 속성이다. 주석 처리한 이유는 수동으로 지정하지 않아도 자동으로 적용이 되기때문에 굳이 수동으로 했다가 에러 생기면 골치아프다.
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.c3p0.min_size" value="5"/>
<property name="hibernate.c3p0.max_size" value="20"/>
<property name="hibernate.c3p0.timeout" value="500"/>
<property name="hibernate.c3p0.idle_test_period" value="2000"/>
이 부분도 마찬가지로 필수 property는 아니다. 하지만 수동으로 지정할 때가 필요할 속성들이라고 생각해서 넣었다.
4. datasource-context.xml
나는 DB connect 관련 resource는 따로 context 파일을 분리했다. 개인적으로 이게 편하다.
이전 글에 나와있지만, 저렇게 분리해도 되나 싶을 수도 있다.
그부분은 web.xml에 servlet 설정을 보면된다.
밑줄친 부분을 보면 appServlet 디렉토리에 context.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:tx="http://www.springframework.org/schema/tx"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.11.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/datasource.properties</value>
</list>
</property>
</bean>
<bean id="jobcallDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${JOBCALL_URL}" />
<property name="username" value="${POOZIM_USER}" />
<property name="password" value="${POOZIM_PASSWORD}" />
<property name="maxActive" value="10" />
<property name="maxIdle" value="5" />
<property name="maxWait" value="10000" />
<property name="validationQuery" value="SELECT 1" />
<property name="testOnBorrow" value="true" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="7200000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="60" />
<property name="logAbandoned" value="true" />
</bean>
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="jobcallDataSource"></property>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"></property>
</bean>
<jpa:repositories base-package="com.poozim.jobcall.repository" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
</beans>
datasource-context.xml 내용이다. 먼저 Namespace에 JPA를 추가해주어야한다. 추가 안하면 <jpa>를 사용못해서 설정 할 수 없다.
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/datasource.properties</value>
</list>
</property>
</bean>
이부분은 밑에 dbconnect 설정에서 db정보 가리기 위해 properties 파일 사용을 정의한 부분이다. 신경쓰지않아도 된다.
<bean id="jobcallDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${JOBCALL_URL}" />
<property name="username" value="${POOZIM_USER}" />
<property name="password" value="${POOZIM_PASSWORD}" />
<property name="maxActive" value="10" />
<property name="maxIdle" value="5" />
<property name="maxWait" value="10000" />
<property name="validationQuery" value="SELECT 1" />
<property name="testOnBorrow" value="true" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="7200000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="60" />
<property name="logAbandoned" value="true" />
</bean>
dbcp로 디비에 연결하는 datasource 설정이다.
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="jobcallDataSource"></property>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"></property>
</bean>
<jpa:repositories base-package="com.poozim.jobcall.repository" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
jpaVendorAdapter와 datasource로 EntityManagerFactory를 설정하고있다.
그 밑에 jpa:repositories를 설정하고 있는데 repositories의 패키지를 설정해주지 않으면 프로젝트 시작시 Repository에 대한 빈생성을 못하기 때문에 지정을 해야한다.
근데 내 경우는 이부분에서 계속 org/springframework/core/metrics 관련 오류가 났는데 Spring의 버전과 spring-data-jpa의 버전이 호환이 되지 않아 발생한 에러인듯 하다. 그럴땐 spring의 버전을 올리거나 spring-data-jpa의 버전을 내려주면된다.
5. Entity
package com.poozim.jobcall.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import lombok.Data;
@Data
@Entity //
@Table(name="Work") // 테이블 명과 클래스 명이 다른 경우 사용
public class Work {
@Id // 고유 값인듯
@GeneratedValue //auto increament 되는 값
int seq;
int member_seq;
String title;
String code;
String email;
String useyn;
String regdate;
String register;
@Transient //영속 제외 필드
String search;
}
@Data는 롬복 어노테이션으로 getter/setter를 자동으로 넣어주는 어노테이션이다.
@Entity는 디비의 데이터가 매핑될 객체 Entity를 지정한다.
@Table은 테이블과 매핑될 객체의 이름이 다를경우 사용하면된다.
@Id는 테이블의 기본키 값에 매칭되는 필드를 지정하는 어노테이션이다.
@GeneratedValue는 db의 auto_increament, 자동으로 값을 지정하는 어노테이션이다.
사실 좀 더 정확히 말하면
@GeneratedValue(strategy=GenerationType.AUTO)이런 식으로 기본키 생성에 대해 4가지 설정을 할 수 있다.
AUTO
IDENTITY
SEQUENCE
TABLE
하지만 아직 잘 몰라서 추후에 알아보고 글을 수정하기로 한다.
@Transient는 테이블의 컬럼과 매핑되지 않는, 영속성에서 제외시킬 필드를 지정한다.
6. Repository
위에 프로젝트 구조에서 봤듯이 일단 WorkRepository 하나만 만들었다.
package com.poozim.jobcall.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.poozim.jobcall.model.Work;
public interface WorkRepository extends JpaRepository<Work, Integer>{
public List<Work> findAll();
}
다른 어노테이션 없이 spring-data-jpa의 JpaRepository를 상속받는 것만으로도 사용이 가능하다.
코드에서는 단순히 selectall 해오는 메서드 하나만 넣어봤다.
(참고로 커스텀하는게 아니면 메서드 안넣어도 다 사용할 수 있다)
테스트
컨트롤러에 간단하게 메서드를 하나 넣고 테스트 해보기로 한다.
결과
미리 넣어돈 test데이터가 잘 넘어온다.
'Spring' 카테고리의 다른 글
Spring JPA @Query 사용하기 JPQL (0) | 2021.10.27 |
---|---|
Spring Interceptor 적용하기 (0) | 2021.10.25 |
Spring XSS Filter 개발 및 적용 (3) | 2021.10.19 |
Spring Bean Scope 빈 스코프 (0) | 2021.10.12 |
Spring AOP (Aspect Oriented Programming) (0) | 2021.10.12 |