JPA - 하이버네이트와 스프링 연동

2021. 11. 17. 16:26 Java 관련/JPA


Pom.xml을 통해 관련 의존성을 추가하고 root-context.xml, servlet-context.xml 파일 설정을 통해 JPA와 스프링프레임워크를 연동합니다. 이어서 컨트롤러, 서비스, 도메인, 레파지토리 단위로 나눠 스프링프레임워크 애플리케이션을 개발합니다.

pom.xml

의존성을 추가해 줍니다.

<?xml version="1.0" encoding="UTF-8"?>
<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>jpabook</groupId>
    <artifactId>ch11-jpa-shop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>jpa-shop</name>
    <packaging>war</packaging>
​
    <properties>
​
        <!-- 기본 설정 -->
        <java.version>1.6</java.version>
        <!-- 프로젝트 코드 인코딩 설정 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
​
        <!-- 스프링 프레임워크 버전 -->
        <spring-framework.version>4.1.6.RELEASE</spring-framework.version>
        <!-- JPA, 하이버네이트 버전 -->
        <hibernate.version>4.3.10.Final</hibernate.version>
        <!-- 데이터베이스 버전 -->
        <tomcat-jdbc.version>7.0.57</tomcat-jdbc.version>
        <h2db.version>1.4.187</h2db.version>
        <!-- JSP, WEB 버전 -->
        <jsp.version>2.2</jsp.version>
        <jstl.version>1.2</jstl.version>
        <servlet.version>3.0.1</servlet.version>
        <!-- 로그 버전 -->
        <logback.version>1.1.1</logback.version>
        <slf4j.version>1.7.6</slf4j.version>
        <!-- 테스트 버전 -->
        <junit.version>4.12</junit.version>
​
    </properties>
​
    <dependencies>
​
        <!-- 스프링 MVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>
​
        <!-- 스프링 ORM -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>
​
        <!-- JPA, 하이버네이트 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
​
        <!-- H2 데이터베이스 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2db.version}</version>
            <scope>runtime</scope>
        </dependency>
​
        <!-- 커넥션 풀 -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
            <version>${tomcat-jdbc.version}</version>
            <scope>compile</scope>
        </dependency>
​
        <!-- WEB -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>${jstl.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.1</version>
            <scope>provided</scope>
        </dependency>
​
        <!-- 로깅 SLF4J & LogBack -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
            <scope>runtime</scope>
        </dependency>
​
        <!-- 테스트 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-framework.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
​
    </dependencies>
​
    <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
              <source>${java.version}</source>
              <target>${java.version}</target>
            </configuration>
          </plugin>
          
          <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <path>/</path>
                <uriEncoding>UTF-8</uriEncoding>
            </configuration>
          </plugin>
                              
        </plugins>
    </build>
</project>

 

xml 구성

기본적으로 STS를 이용한다면 root-context.xml와 servlet-context.xml 파일에 관한 설정이 web.xml 파일이 포함되어 있는 것을 알 수 있습니다.

  • root-context.xml : 비즈니스 로직, 도메인 계층, 서비스 계층, 데이터 저장 계층을 담당하고 있습니다.
  • servlet-context.xml : MVC 설정을 포함해 웹 계층에 대한 설정을 담당합니다 .

root-context.xml 구성

root-context.xml을 구성하면 다음과 같이 구성합니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    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">
​
    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
    
    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />
​
    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/resources/**" location="/resources/" />
​
    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
    
    <context:component-scan base-package="kr.nchurch.app" />
    
    
</beans:beans>

servlet-context.xml 구성

MariaDB를 이용한 servlet-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: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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
​
    <tx:annotation-driven/>
​
    <context:component-scan base-package="JPA.examples"/>
​
​
    <bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/myapp?useUnicode=true&amp;characterEncoding=utf8" />
        <property name="username" value="root"/>
        <property name="password" value=""/>
    </bean>
​
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
​
    <!-- JPA 예외를 스프링 예외로 변환 -->
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
​
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="JPA.examples.domain"/> 
        <!-- @Entity 탐색 시작 위치 -->
        <property name="jpaVendorAdapter">            
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.use_sql_comments">true</prop>
                <prop key="hibernate.id.new_generator_mappings">true</prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
            </props>
        </property>
    </bean>
​
</beans>

만약 h2 데이터베이스라면 dataSource 빈은 다음과 같이 설정합니다.

<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource">
        <property name="driverClassName" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:mem:jpashop"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

 

계층 분리를 고려한 개발

스프링 프레임워크 연동이 끝나면 계층 나눠 개발을 진행합니다.

  • 컨트롤러
  • 서비스
  • 레파지토리
  • 도메인

 

컨트롤러

package jpabook.jpashop.web;
​
import jpabook.jpashop.domain.Address;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.service.ItemService;
import jpabook.jpashop.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
​
import java.util.List;
​
​
@Controller
public class MemberController {
​
    @Autowired MemberService memberService;
    @Autowired ItemService itemService;
​
    @RequestMapping(value = "/members/new", method = RequestMethod.GET)
    public String createForm() {
        return "members/createMemberForm";
    }
​
    @RequestMapping(value = "/members/new", method = RequestMethod.POST)
    public String create(Member member, String city, String street, String zipcode) {
​
        Address address = new Address(city, street, zipcode);
        member.setAddress(address);
        memberService.join(member);
        return "redirect:/";
    }
​
    @RequestMapping(value = "/members", method = RequestMethod.GET)
    public String list(Model model) {
​
        List<Member> members = memberService.findMembers();
        model.addAttribute("members", members);
        return "members/memberList";
    }
​
}

 

서비스

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
​
import java.util.List;
​
​
@Service
@Transactional
public class MemberService {
​
    @Autowired
    MemberRepository memberRepository;
​
    /**
     * 회원 가입
     */
    public Long join(Member member) {
​
        validateDuplicateMember(member); //중복 회원 검증
        memberRepository.save(member);
        return member.getId();
    }
​
    private void validateDuplicateMember(Member member) {
        List<Member> findMembers = memberRepository.findByName(member.getName());
        if (!findMembers.isEmpty()) {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }
​
    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers() {
        return memberRepository.findAll();
    }
​
    public Member findOne(Long memberId) {
        return memberRepository.findOne(memberId);
    }
}

 

레파지토리

import org.springframework.stereotype.Repository;
​
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
​
​
@Repository
public class MemberRepository {
​
    @PersistenceContext
    EntityManager em;
​
    public void save(Member member) {
        em.persist(member);
    }
​
    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }
​
    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
​
    public List<Member> findByName(String name) {
        return em.createQuery("select m from Member m where m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();
    }
}

 

도메인

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
​
@Entity
public class Member {
​
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
​
    private String name;
​
    @Embedded
    private Address address;
​
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<Order>();
​
​
    public Long getId() {
        return id;
    }
​
    public void setId(Long id) {
        this.id = id;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public Address getAddress() {
        return address;
    }
​
    public void setAddress(Address address) {
        this.address = address;
    }
​
    public List<Order> getOrders() {
        return orders;
    }
​
    public void setOrders(List<Order> orders) {
        this.orders = orders;
    }
​
    @Override
    public String toString() {
        return "Member{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
}

 

참고



출처: https://happygrammer.tistory.com/150?category=891896 [happygrammer]