'

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Study22]JPA db CRUD실습
    FISA 2026. 1. 28. 16:16

     

    JDBC, JPA, Spring Data JPA의 관계와 역할 정리

    자바에서 데이터베이스를 다룰 때 가장 먼저 등장하는 개념이 JDBC다.

     

    JDBC(Java Database Connectivity)는 자바에서 데이터베이스에 직접 접근하기 위한 가장 기본적인 표준 API다.

     

    자바 애플리케이션은 JDBC를 통해 SQL을 데이터베이스로 전달하고, 그 결과를 받아온다.

     

    이 과정에서 개발자는 커넥션 생성, SQL 작성, 파라미터 바인딩, ResultSet 처리, 리소스 해제까지 모든 과정을 직접 제어해야 한다.이 말은 곧, JDBC는 가장 유연하지만 가장 번거로운 방식이라는 뜻이기도 하다. 단순한 CRUD 하나를 구현하더라도 반복적인 코드가 매우 많이 발생하고, 비즈니스 로직보다 SQL 처리 코드가 더 눈에 띄는 상황이 자주 생긴다. 하지만 그만큼 JDBC는 내부 동작이 명확하고, 데이터베이스와의 상호작용을 가장 직접적으로 이해할 수 있는 계층이다. 그래서 JDBC는 모든 자바 기반 ORM 기술의 기초 토대라고 볼 수 있다.

     

     

    이러한 JDBC의 단점을 줄이기 위해 등장한 개념이 JPA다. JPA(Java Persistence API)는 자바 객체와 데이터베이스 테이블을 매핑하기 위한 ORM(Object-Relational Mapping) 표준이다. JPA의 핵심 아이디어는 “SQL 중심 개발에서 객체 중심 개발로 옮겨가자”는 것이다. 개발자는 더 이상 테이블과 SQL을 직접 다루기보다는, 자바 객체를 다루고, JPA가 내부적으로 SQL을 생성하고 실행하도록 맡긴다.

    실제로 데이터베이스와 통신하며 SQL을 실행하는 역할은 Hibernate 같은 JPA 구현체가 담당한다. 내부적으로는 여전히 JDBC를 사용하지만, 그 사실을 개발자가 직접 신경 쓸 필요는 없다. 이 덕분에 반복적인 CRUD 코드가 크게 줄어들고, 객체 단위의 설계가 자연스러워진다.

    다만 JPA 역시 완전히 마법 같은 기술은 아니다. 엔티티 상태 관리, 영속성 컨텍스트, 지연 로딩, 트랜잭션 범위 같은 개념을 이해하지 못하면 오히려 디버깅이 어려워질 수 있다. 즉, JPA는 JDBC 위에 올라간 한 단계 높은 추상화 계층이라고 보는 것이 정확하다.

    여기서 한 단계 더 나아간 것이 Spring Data JPA다. Spring Data JPA는 JPA를 더 쉽게, 더 자동화해서 쓰기 위한 스프링 프로젝트다. JPA 자체는 여전히 Repository 구현, EntityManager 사용, 쿼리 관리 등을 개발자가 일정 부분 직접 처리해야 하지만, Spring Data JPA는 이마저도 대부분 자동으로 처리해준다.

     

    대표적인 예가 Repository 인터페이스다. 단순히 JpaRepository를 상속받기만 해도 기본적인 CRUD 메서드가 자동으로 제공된다. 심지어 메서드 이름 규칙만 지켜도 쿼리가 자동으로 생성된다. 이로 인해 개발자는 데이터 접근 로직보다 도메인 설계와 비즈니스 로직에 집중할 수 있게 된다.

    정리하면 구조는 다음과 같다.

     


    JDBC는 데이터베이스 접근의 가장 기본적인 기술이고, JPA는 JDBC 위에서 동작하는 ORM 표준, Spring Data JPA는 JPA를 스프링 환경에서 극도로 생산성 높게 사용하기 위한 추상화 레이어다.
    즉, “JDBC를 직접 쓰지 않더라도, JPA와 Spring Data JPA는 내부적으로 JDBC 위에서 동작한다”는 점이 핵심이다.

     

    션 풀링(Connection Pooling)이란 무엇인가

    데이터베이스와 통신하기 위해서는 반드시 Connection이 필요하다. 이 Connection은 단순한 객체가 아니라,
    DB 서버와 TCP 소켓을 맺고, 인증을 수행하고, 세션을 생성하는 비용이 매우 큰 자원이다.
    만약 요청이 들어올 때마다 매번 새로운 Connection을 생성하고, 작업이 끝날 때마다 바로 닫아버린다면 성능은 급격히 나빠진다.

    이 문제를 해결하기 위해 등장한 개념이 커넥션 풀링(Connection Pooling)이다.

     


    커넥션 풀링이란, 애플리케이션이 시작될 때 미리 여러 개의 DB Connection을 생성해 풀(pool)에 보관해 두고,
    요청이 들어오면 그중 하나를 빌려주고, 사용이 끝나면 닫는 것이 아니라 다시 풀로 반환하는 방식이다.

    즉, Connection을 매번 새로 만드는 자원이 아니라
    재사용되는 공유 자원으로 관리하는 전략이다.

     

    JDBC에선 기본 기능이 아니기 때문에, 직집 구현해야해요. DriverManager.getConnection()을 직접 호출하면 매번 새로운 Connection이 생성되기때문에 별도의 커넥션 풀 라이브러를 사용해서 구현해야 합니다.

     

    JPA에선 직접 구현하지 않습니다. JPA는 ORM 표준이고, 실제 DB 연결은 구현체(Hibernate 등)가 담당합니다.Hibernate 같은 JPA 구현체는 내부적으로 “DB에 연결할 때 커넥션 풀을 사용하라”는 설정을 읽고, 지정된 커넥션 풀 라이브러리를 통해 Connection을 관리합니다.

    즉, JPA를 쓴다고 해서 커넥션 풀이 자동으로 생기는 것은 아니지만, 실제 운영 환경에서는 반드시 커넥션 풀과 함께 사용된다고 이해하면 됩니다.

     

    Spring data JPA에선 자동으로 해준다네요~

    왜 커넥션 풀링이 중요한가

    커넥션 풀링이 없으면:

    • 요청마다 Connection 생성/해제 → 심각한 성능 저하
    • 동시 접속자가 늘면 DB가 먼저 죽음
    • 트래픽이 조금만 몰려도 응답 지연 발생

    커넥션 풀링이 있으면:

    • Connection 생성 비용 최소화
    • 동시 요청을 안정적으로 처리
    • DB 자원을 제한된 개수로 통제 가능

    그래서 실무 환경에서 커넥션 풀링 없는 DB 연동은 거의 존재하지 않는다고 봐도 무방합니다.

     

     

    1. Entity만들기

    우선 Entity를 만들어 줍니다.

    package model.entity;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.SequenceGenerator;
    import javax.persistence.Table;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    @Entity
    @Table(name = "customer")
    @Getter
    @Setter
    @ToString
    public class Customer {
    
    
        @Id
        @SequenceGenerator(
            name = "cust_seq_gen",
            sequenceName = "CUST_SEQ",
            allocationSize = 1
        )
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cust_seq_gen")
        private Long custId;
    
        private String cust_name;
        private int custGrade;
    }

    - 오라클 데이터 베이스를 써서, SequenceGenerator설정을 저렇게 따로 해줘야 한다고 합니다.

     

     

    2. Persistence.xml 생성

    JPA를 위한 영속성.xml을 만들어줍니다. 여기서 JDBC에서 했던 DB연결과, 커넥션 풀링을 위한 Hibernate를 설정해줍니다.

    커넥션풀링을 얘가 담당해줘서 JDBC할 때처럼 일일이 해 줄 필요가 없어요.

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.1"
    	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_1.xsd">
    	<!-- <persistence-unit name="step10_JPA"> </persistence-unit> -->
    
    
    	<persistence-unit name="jpaPU"
    		transaction-type="RESOURCE_LOCAL">
    		<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    		<class>model.entity.Member</class>
    		<class>model.entity.Customer</class>
    		
    		<properties>
    			<!-- JDBC -->
    			<property name="javax.persistence.jdbc.driver"
    				value="oracle.jdbc.OracleDriver" />
    			<property name="javax.persistence.jdbc.url"
    				value="jdbc:oracle:thin:@localhost:1521:xe" />
    			<property name="javax.persistence.jdbc.user" value="user01" />
    			<property name="javax.persistence.jdbc.password"
    				value="user01" />
    
    			<!-- Hibernate -->
    			<property name="hibernate.dialect"
    				value="org.hibernate.dialect.Oracle10gDialect" />
    			<property name="hibernate.hbm2ddl.auto" value="update" />
    			<property name="hibernate.format_sql" value="true" />
    		</properties>
    
    	</persistence-unit>
    
    
    
    </persistence>

     

     

     

    2. JpaMain.java 생성

    이제 데이터를 조작해줄 메인 자바 파일을 하나 만들어 줍니다.

    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    
    import model.entity.Customer;
    import model.entity.Member;
    
    public class JpaMain {
    
    	
    	
    	
    	
    	
    	public static void main(String[] args) {
    	
    		customCreate();
    		//customUpdate();
    		//customRead();
    		//customDelete();
    		
    		
    	}
    	
    	
    
    	
    	public static void customCreate() {
    
    		EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaPU");
    		EntityManager em = emf.createEntityManager();
    	   EntityTransaction tx = em.getTransaction();
    
    	    tx.begin();
    
    	    
    		Customer cust1 = new Customer();
    		cust1.setCust_name("서지혜");
    		cust1.setCustGrade(1);
    	
    		em.persist(cust1);
    		
    		Customer cust2 = new Customer();
    		cust2.setCust_name("홍길동");
    		cust2.setCustGrade(2);
    		
    		em.persist(cust2);
    
    	   tx.commit(); 
    	   
    		em.close();
    		emf.close();
    	}
    	public static void customRead() {
    
    		EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaPU");
    		EntityManager em = emf.createEntityManager();
    		
    		Customer c1 = em.find(Customer.class, 1L); // SELECT 실행 서지혜출력
    		System.out.println(c1);
    		
    		em.clear();
    		
    		Customer c2 = em.find(Customer.class, 2L); // SELECT 다시 실행 홍길동 출
    		System.out.println(c2);
    		
    		
    		em.close();
    		emf.close();
    	}
    	public static void customUpdate() {
    
    		EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaPU");
    		EntityManager em = emf.createEntityManager();
    	   EntityTransaction tx = em.getTransaction();
    
    	   tx.begin();
    
    		Customer cust1 = em.find(Customer.class, 1L);
    		cust1.setCust_name("SeoJiHye");
    		cust1.setCustGrade(3);
    		
    	   tx.commit(); 
    		em.close();
    		emf.close();
    	}
    
    	
    	public static void customDelete() {
    
    		EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaPU");
    		EntityManager em = emf.createEntityManager();
    	    EntityTransaction tx = em.getTransaction();
    
    	    tx.begin();
    		Customer cust1 = em.find(Customer.class, 1L);
    		Customer cust2 = em.find(Customer.class, 2L);
    
    		em.remove(cust1);
    		em.remove(cust2);
    		
    		
    	   tx.commit(); 
    
    		em.close();
    		emf.close();
    	}

     

    1. 항상 아래와 같은 방식으로 Create Update Delete해줍니다.

    	EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaPU");
    		EntityManager em = emf.createEntityManager();
    	   EntityTransaction tx = em.getTransaction();
    
    	    tx.begin();
    
    	    
    
    	   tx.commit(); 
    	   
    		em.close();
    		emf.close();

     

    엔티티 매니저 팩토리가 jpaPU라는 데이터베이스와 연결해주는 역할을 합니다.

    TX는 트랜잭션이라고, 항상 커밋을 위해선 추가해줘야 하는 것 같아요.

    그리고 close로 자원반환을 해줍니다.

     

    2. Create시, Model 객체를 생성해서 setter getter를 통하여 값 설정 후 persist해줍니다.

    커밋은 필수!!

     

    3. Read시에, find로 객체를 찾아서 해줘야합니다. 그리고 셀렉트문은 한 번만 되기 때문에, clear로 풀 초기화를 해줘야 합니다.

     

    4. update시 Create와 비슷하게 find로 객체 찾아주고 지정해 준 다음에 set해줘야합니다.

     

    5. Delete시, remove()로 지우고 꼭!!Commit을 해야합니다.

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    728x90

    댓글

Designed by Tistory.
티스토리 친구하기