관리 메뉴

개발 노트

10/27 Lombok 설치, gradle과 maven 비교, JPA 개념, JPA CRUD, oBootJpa01(JPA코드로 DB에 저장하는 프로그램), iBatis와 Hibernate비교 본문

프로젝트 기반 JAVA 응용 SW개발 : 22.07.19~23.01.20/Spring

10/27 Lombok 설치, gradle과 maven 비교, JPA 개념, JPA CRUD, oBootJpa01(JPA코드로 DB에 저장하는 프로그램), iBatis와 Hibernate비교

hayoung.dev 2022. 11. 15. 16:07

repository > JdbcMemberRepository.java (class, MemberRepository 상속)

package com.oracle.oBootDBConnect.repository;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Repository;

import com.oracle.oBootDBConnect.domain.Member1;

@Repository
public class JdbcMemberRepository implements MemberRepository {
    // JDBC 사용
	/* DataSource를 bean작업으로 해줘야 하기 때문에 root에 SpringConfig.java를 생성한다. */
	private final DataSource dataSource;
	
	public JdbcMemberRepository(DataSource dataSource) {
		  this.dataSource = dataSource;
	}
	
	/* Connection은 외부에서 못쓰게 하기 위해 private로 한다. */ 
	private Connection getConnection() {
		/* connection을 DataSourceUtils로 사용하였기 때문에 DataSourceUtils를 닫아야 한다. */
		return DataSourceUtils.getConnection(dataSource);
	}
	
	@Override
	public Member1 save(Member1 member1) {
	    String sql = "insert into member1(id,name) values(member_seq.nextval,?)";
	    System.out.println("JdbcMemberRepository sql->"+sql);
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            conn = getConnection();
			pstmt = conn.prepareStatement(sql);
	        pstmt.setString(1, member1.getName());
            pstmt.executeUpdate();
    	    System.out.println("JdbcMemberRepository pstmt.executeUpdate After");
            return member1;
		} catch (Exception e) {
            throw new IllegalStateException(e);		
        } finally {
           close(conn, pstmt, rs);
        }
	}

	@Override
	public List<Member1> findAll() {
	       String sql = "select * from member1";
	       Connection conn = null;
	       PreparedStatement pstmt = null;
	       ResultSet rs = null;
	       try {
	            conn = getConnection();
	            pstmt = conn.prepareStatement(sql);
	            rs = pstmt.executeQuery();
	            List<Member1> members = new ArrayList<>();
	            while(rs.next()) {
	                Member1 member = new Member1();
	                member.setId(rs.getLong("id"));
	                member.setName(rs.getString("name"));
	                members.add(member);
	            }
	            return members;
	        } catch (Exception e) {
	            throw new IllegalStateException(e);
	        } finally {
				/* 여러번 쓰게 되면 코드가 복잡해지기 때문에 하단에서 한번에 작성하여 모듈화하여 쓴다. */
	            close(conn, pstmt, rs);
	        }	
	  }
	
	/* close는 두 가지 용도로 쓰인다. 메소드 오버로딩. */
	/* DataSourceUtils를 닫는다. */
	private void close(Connection conn) throws SQLException {
		/* (dataSource)이 데이터소스를 가지고 (conn)연결을 해제(release)시킨다.*/
		DataSourceUtils.releaseConnection(conn, dataSource);
	}
	
	/* close 모듈화한 코드 */
	private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
		try {
			if (rs != null)
				rs.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			if (pstmt != null)
				pstmt.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		try {
			if (conn != null)
				close(conn);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

build.gradle의 dependencies에 하단 코드 추가 후  build gradle 우클릭 > gradle > refresh gradle project

implementation 'org.springframework.boot:spring-boot-starter-jdbc'

(member1 테이블 형태만 있는 상태, 시퀸스 member_seq를 생성한 상태에서 진행)

 

localhost:서버번호/

를 했을 때

root(java > oBootDBConnect > SpringConfig.java) 가 실행된다.

 

출력 결과

회원 가입에 회원1, 회원2를 등록 후 회원 목록을 확인하면 하단과 같이 뜬다.

scott의 member1 테이블에도 저장이 되어있다.

이렇게 설정하면 세션을 종료하여도 없어지지 않는다.

 

 

[lombok 설치]

spring 폴더에 lombok.jar 파일을 저장한다.

 

cmd 창에서 lombok.jar를 저장한 경로로 이동하여 하단을 입력한다.

java -jar lombok.jar

 

STS.exe 경로가 설치되어있는 경로로 설정한다.

eclipse는 알아서 설정된다.

 

설치 성공

 

 

[JPA 개념]

JPA 동작

 

JPA 표준명세와 특징

JPA CRUD

JPA Performance

JPA Transation 지연.

커밋하는 순간 데이터베이스에 insert sql을 모아 보낸다.

 

JPA 구성요소

JPA Entity 생명주기

 

[oBootJpa01]

 

Oracle에서 scottJpa 계정을 만들어준다.

CREATE USER scottJpa IDENTIFIED BY tiger;
GRANT DBA TO scottJpa;

 

 

java > controller, service, repository, domain (package) 생성

application.properties

server.port=8383
# Oracle Connect
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/xe
spring.datasource.username=scottJpa
spring.datasource.password=tiger

# Jpa Setting
spring.jpa.show-sql=true
# none
spring.jpa.hibernate.ddl-auto=create

controller > MemberController.java

package com.oracle.oBootJpa01.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import com.oracle.oBootJpa01.domain.Member;
import com.oracle.oBootJpa01.service.MemberService;

@Controller
public class MemberController {
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	private final MemberService memberService;
	@Autowired
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	/* 조회할 때 @GetMapping 사용 */
	@GetMapping(value="/members/new")
	public String createForm() {
		System.out.println("MemberController /members/new start.. ");
		return "members/createMemberForm";
	}
	
	/*수정, 삭제, 저장할 때 사용 @PostMapping 사용*/
	@PostMapping(value = "members/save")
	public String memberSave(Member member) {
		System.out.println("MemberController memberSave start.. ");
		System.out.println("member.getId()->"+member.getId());
		System.out.println("member.getName()->"+member.getName());
		memberService.memberSave(member);
	   	System.out.println("MemberController memberSave After..");
	    
		return "redirect:/";
	}
	
	@GetMapping(value = "/members")
	public String listMember(Model model) {
		List<Member> memberList = memberService.getListAllMember();
		logger.info("memberList.size -> {}.", memberList.size());
		model.addAttribute("members", memberList);
		return "members/memberList";
	}
}

service > MemberService.java

package com.oracle.oBootJpa01.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpa01.domain.Member;
import com.oracle.oBootJpa01.repository.MemberRepository;

@Service
@Transactional /* JPA는 @Transactional을 꼭 써주어야 한다. */ 
public class MemberService {
	private final MemberRepository memberRepository;
	@Autowired /* DI 쓰는 경우 Autowired 연결 */
	/* 생성자 생성 */
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
	
	// 회원가입 
    public Long memberSave	(Member member) {
    	System.out.println("MemberService memberSave member-->"+member);
		memberRepository.memberSave(member); /* 저장 */
    	System.out.println("MemberService memberSave After..");
		/* 서비스에서 member.getId()이 리턴되는 순간 커밋된다. */
		/* 그래서 트렌젝션을 같은 트렌젝션에 거는 것이 편하므로 트렌젝션을 서비스에 건다. */
    	return member.getId();
    }

	public List<Member> getListAllMember() {
		List<Member> listMember  = memberRepository.findAllMember();
		System.out.println("MemberService getListAllMember listMember.size()->"+listMember.size());
		return listMember;
	}

}

repository > MemberRepository.java

package com.oracle.oBootJpa01.repository;

import java.util.List;

import com.oracle.oBootJpa01.domain.Member;

public interface MemberRepository {
	Member       memberSave(Member member);
	List<Member> findAllMember();

}

domain > Member.java

package com.oracle.oBootJpa01.domain;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "member1")
//Member 이 객체에 대한 테이블이 member1이라는 뜻이다.
/* 객체는 Member가 되지만 table 이름은 member1로 동시에 두 개가 된다. */
public class Member {
		@Id /* primary key 지정. JPA에서는 pk를 반드시 지정해주어야 한다. */
	   private Long   id;
	   private String name;

}

build.gradle

plugins {
	id 'org.springframework.boot' version '2.7.5'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
	id 'java'
}

group = 'com.oracle'
version = 'version1.0'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation      'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation      'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation      'org.springframework.boot:spring-boot-starter-web'
	compileOnly         'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	runtimeOnly         'com.oracle.database.jdbc:ojdbc8'
	testImplementation  'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

상단 코딩 후 build.gradle > gradle > refresh gradle project

 

repository > JpaMemberRepository.java (인터페이스, MemberRepository 상속)

package com.oracle.oBootJpa01.repository;

import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.stereotype.Repository;

import com.oracle.oBootJpa01.domain.Member;

@Repository /*DAO*/
public class JpaMemberRepository implements MemberRepository {
	// JPA DML --> EntityManager 필수 
	private final EntityManager em;
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}	

	@Override
	public Member memberSave(Member member) {
		// 저장 method
		// 하단 코드를 쓰면 DB에 저장이 된다.
		em.persist(member);
		System.out.println("JpaMemberRepository memberSave member After..");
		return member;
	}

	@Override
	public List<Member> findAllMember() {
		/* 하단의 sql문에서 Member는 table명이 아니라 객체 entity이다. 주의해야 함. */
		/* list로 가져오고 싶을 때 .getResultList(); 을 사용하면 List<Member> 타입으로 memberList에 넣어준다. */
		/* sql문이 아닌 jpql문이다. */
		List<Member> memberList = em.createQuery("select m from Member m", Member.class )
				                    .getResultList(); 
		System.out.println("JpaMemberRepository findAllMember memberList.size()->"+memberList.size());
		return memberList;
	}

}

resource > static > index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <a href="/members/new">Member 신규 생성</a><p>
    <a href="/members">Member List 조회</a>
</body>
</html>

repository > members(package) > createMemberForm.html

<!DOCTYPE html>
<html  xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>JPA 회원 등록</h1>
	<div class="container">
	    <form action="/members/save" method="post">
	       ID : <input type="text" id="id"   name="id"   required="required"><p>
	           이름  : <input type="text" id="name" name="name" placeholder="이름을 입력하세요">
	         <button type="submit">등록</button>
	    </form>
	</div> <!-- /container -->
</body>
</html>

repository > members(package) > memberList.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container">
    <div>
        <table border="1">
            <thead>
            <tr>
                <th>No</th>
                <th>이름</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="member : ${members}">
                <td th:text="${member.id}"></td>
                <td th:text="${member.name}"></td>
            </tr>
            </tbody>
        </table>
    </div>
</div> <!-- /container -->
</body>
</html>

 

실행 순서

1. Member  신규 생성 : /members/new

1) MemberController.java에 의해 members/createMemberForm(html)로 이동한다.

2) submit로 등록하면 id와 name이 post 방식으로 들어가 /members/save(controller)로 이동한다.

 

3) 컨트롤러의 코드에 따라 

memberService.memberSave(member); 를 실행하고 

그 전페이지를 리턴한다.

(1) 여기에서 .memberSave는 서비스에 있다. 

memberRepository.memberSave(member); 를 실행하고 

member.getID를 리턴한다.

(1-1) 여기에서 .memberSave는 repository에 있다. 

em.persist(member);를 실행하고 member를 리턴한다.

persist는 저장 메소드이다. 즉 저장을 하고 그 member를 리턴한다. 

 

그러면 다시 (1)로 돌아와 member.getID를 리턴하게 되고

다시 그 앞으로 돌아와 그 전페이지를 리턴한다. 

 

2. Member List 조회 : /members

1) 컨트롤러의 /members코드에 따라 

List<Member> memberList = memberService.getListAllMember();
model.addAttribute("members", memberList);
return "members/memberList";

코드를 실행하게 된다.

2) getListAllMember는 service 코드에 따라

List<Member> listMember  = memberRepository.findAllMember();
return listMember;

코드를 실행하게 된다.

3) findAllMember는 repository 코드에 따라

List<Member> memberList = em.createQuery("select m from Member m", Member.class ).getResultList(); 
return memberList;

코드를 실행하게 된다.

select한 코드를 list로 가져와 memberList에 저장하고 memberList를 리턴한다.

 

다시 2)로 돌아가서 memberList를  listMember에 넣고 리턴한다.

다시 1)로 돌아가서 리턴값을 memberList에 넣고 members/memberList html을 리턴한다.

 

4) members/memberList.html에 따라 값이 출력된다.

 

scottJpa 계정의 member1 테이블 모습

참고로 console 창에 나오는

을 실행해도 table select이 실행이 되어 결과가 똑같이 나온다.

 

하단 코드는 @Transactional이 끝날 때 실행된다.

트렌젝션의 원칙(원자성, 일관성, 독립성, 지속성) 때문에 commit이나 rollback이 한 단위로 돼야 한다.

 

application.properties 코드에서 11줄의 create는 코딩 단계에서만 해준다. 잘못 건드려서 데이터가 날라갈 수 있기 때문이다. 코딩이 다 되었으면 none으로 설정해주어야 한다. 

(중요 : 표시한것 기억하기) 

iBatis는 SQL Mapping(Partial ORM), Hibernate는 OR Mapping(Full ORM)

 

 

반응형