국비학원_67일차(Spring JPA, DB연결, 롬북)

써니·2022년 10월 27일
0

spring

목록 보기
9/23

🍃Boot DB 연결

🔻oBootDBConnect

🤝회원가입(CRUD)

🔎HomeController를 만들었기 때문에 index.html를 타지 않는다.


👇 수정사항

[com.oracle.oBootDBConnect.controller]

  • MemberController.java
package com.oracle.oBootDBConnect.controller;

import java.util.List;

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.oBootDBConnect.domain.Member1;
import com.oracle.oBootDBConnect.service.MemberService;

@Controller
public class MemberController {
	private final MemberService memberService;
	@Autowired
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	@GetMapping(value = "/members/memberForm")
	public String createMemberForm() {
		System.out.println("MemberController /members/memberForm start...");
		return "members/createMemberForm";
	}
	
	@PostMapping(value = "/members/newSave")
	public String memberSave(Member1 member1) {
		System.out.println("MemberController memberSave start...");
		memberService.memberSave(member1);
		return "redirect:/";
	}
	
	// 전체 조회 화면
	@GetMapping(value = "/members/memberList")
	public String memberLists(Model model) {
		List<Member1> memberList = memberService.findMembers();
		model.addAttribute("members", memberList);
		return "members/memberList";
	}
}	

[templates.members]

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



🙋‍♀️회원가입-JDBC

[com.oracle.oBootDBConnect.repository]

  • JdbcMemberRepository
    final -> 고정 / 바꾸지 않는다
    DB - Member1 table 사용
package com.oracle.oBootDBConnect.repository;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
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 사용
	private final DataSource dataSource;
	
	public JdbcMemberRepository(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	private Connection getConnection() {
		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 save 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() {
		// TODO Auto-generated method stub
		return null;
	}
	
	private void close(Connection conn) throws SQLException {
		DataSourceUtils.releaseConnection(conn, dataSource);
	}
	
	private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
		try {
			if( rs    != null )   rs.close();
		} catch (SQLException e) {
			System.out.println(e.getMessage());
		}
		
		try {
			if( pstmt != null ) pstmt.close();
		} catch (SQLException e) {
			System.out.println(e.getMessage());
		}
		
		try {
			if( conn  != null ) close(conn);
		} catch (SQLException e) {
			System.out.println(e.getMessage());
		}
	}

}

🔎 만약 @Repository를 두개 선언한다면?

❌오류발생

✍ 해결방법

MemoryMemberRepository -> @Repository 삭제


❌오류 발생

✍ 해결방법

  1. String sql = "insert into member1(id, name) values(member_seq.nextval,?)"; 테이블 명 오류
  2. member_seq를 만들지 않아서 오류

🌍환경작업

[com.oracle.oBootDBConnect]

  • SpringConfig.java
    환경작업
package com.oracle.oBootDBConnect;

import javax.sql.DataSource;

import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {
	private final DataSource dataSource;
	
	public SpringConfig(DataSource dataSource) {
		this.dataSource = dataSource;
	}
}


🧐회원조회-JDBC

[com.oracle.oBootDBConnect.repository]

  • JdbcMemberRepository
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 사용
	private final DataSource dataSource;
	
	public JdbcMemberRepository(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	private Connection getConnection() {
		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 save 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);
		}
	}
	
	private void close(Connection conn) throws SQLException {
		DataSourceUtils.releaseConnection(conn, dataSource);
	}
	
	// close 오류에 대한 메소드
	private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
		try {
			if( rs    != null )   rs.close();
		} catch (SQLException e) {
			System.out.println(e.getMessage());
		}
		
		try {
			if( pstmt != null ) pstmt.close();
		} catch (SQLException e) {
			System.out.println(e.getMessage());
		}
		
		try {
			if( conn  != null ) close(conn);
		} catch (SQLException e) {
			System.out.println(e.getMessage());
		}
	}

}


DI의 장점

MemoryMemberRepository, JdbcMemberRepository의 @Repository 선언을 삭제했을 때

[com.oracle.oBootDBConnect]

  • SpringConfig.java
  1. DB 연결 -> return new JdbcMemberRepository(dataSource);
  2. Memory 연결-> return new MemoryMemberRepository();
package com.oracle.oBootDBConnect;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.oracle.oBootDBConnect.repository.JdbcMemberRepository;
import com.oracle.oBootDBConnect.repository.MemberRepository;
import com.oracle.oBootDBConnect.repository.MemoryMemberRepository;

@Configuration
public class SpringConfig {
	private final DataSource dataSource;
	
	public SpringConfig(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	@Bean 
	MemberRepository memberRepository() {
		// return new JdbcMemberRepository(dataSource);
		return new MemoryMemberRepository();
	}
}

⭐결론⭐

❗ 스프링 빈을 등록하는 2가지 방법
1. 컴포넌트 스캔과 자동 의존관계 설정
2. 자바 코드로 직접 스프링 빈 등록하기

@Repository를 선언하지 않고 SpringConfig에서 @Bean 선언하여 환경작업을 하면 굳이 따로 선언안해도 configuration에서 검증을 해준다.




🌹Lombok

Lombok이란 어노테이션 기반으로 코드를 자동완성 해주는 라이브러리이다. 롬북참고

💻 롬북사용법

❗모든 팀원이 lombok 설치 해야 함

  1. lombok.jar를 스프링 폴더에 넣는다

  2. sts 종료

  3. cmd
    -> cd C:\spring
    java -jar lombok.jar

  4. 롬북 설치
    -> specify location
    sts.exe(select)
    install/update


Lombok annotation




🤔gradle과 Maven 비교

참고




⭐JPA

( Java Persistence API )

  • 자바 진영의 ORM 기술 표준

📌JPA 개념도

내부적으로 JDBC API를 사용


📌JPA 표준명세와 특징

  • 하이버네이트

📎 JPA 특징

SQL별로 표준을 지키지 않은 데이터베이스만의 고유한 기능, 데이터베이스 방언

  • 객체 중심으로 개발
  • 생산성
  • 유지보수
  • 성능
  • 데이터 접근 추상화와 벤더 독립성 : 다른 함수명
  • 표준 : 페이징 처리

📌JPA CRUD

  1. 저장: jpa.persist(emp)
  2. 조회: Emp emp= jpa.find(empno)
  3. 수정: emp.setEname(“변경할 이름”)
  4. 삭제: jpa.remove(emp)

📌JPA Performance(캐시)

  • 1차 cache와 동일성 : 같은 트랜잭션안에서 같은 엔티티를 반환하기 때문에 조회 성능 향상

📌JPA Transaction지연

  • 트랜잭션 단위로 커밋을 한다.

⭐지연로딩

  • 객체가 실제 사용될 때 로딩

⭐즉시로딩

  • JOIN SQL로 한번에 연관된 객체까지 미리 조회

📌JPA 생명주기

📎 JPA 영속성 Context

  1. 엔티티 조회, 1차 캐시(엔티티를 생성한 상태-비영속)
  2. 엔티티를 영속 1차 캐시에 저장됨
  3. 1차 캐시에서 조회
  4. 데이터베이스에서 조회



🔻oBootJpa01

🤝회원가입(JPA)

Spring Data JPA 추가

💾 scottJpa user 생성


  • build.gradle
    lombok 설정 추가
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

📂 src/main/resources

  • application.properties

create는 기존 테이블 삭제 후 다시 생성한다❗

username -> scottJpa 변경

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
spring.jpa.hibernate.ddl-auto=create

⭐JPA DB 스키마 자동생성

테이블을 바꾸지 않는다면 create 후 none으로 변경


[com.oracle.oBootJpa01.controller]

  • MemberController.java
package com.oracle.oBootJpa01.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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 final MemberService memberService;
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	@Autowired
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	@GetMapping(value = "/members/new")
	public String createFrom() {
		System.out.println("MemberController /members/new start...");
		return "/members/createMemberForm";
	}
	
	@PostMapping(value = "/members/save")
	public String memberSave(Member member) {
		System.out.println("MemberController /members/save 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:/";
	}
}

👉객체를 보고싶을 때 방법

  1. dto에 toString을 걸어 준다.
  2. [ lombok ] dto에 @data 어노테이션을 걸어준다(@Setter, @Getter 없어도 됨)

[com.oracle.oBootJpa01.domain]

  • Member.java

    @Entity, lombok-getter, setter 추가
    @Id -> Id를 primary-key로 지정

package com.oracle.oBootJpa01.domain;

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

import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
// @Data -> 객체를 보고 싶을 때 2
public class Member {
	@Id
	private Long id;
	private String name;
    
    // toString -> 객체를 보고 싶을 때 1
    @Override
	public String toString() {
		String returnStr = "";
		returnStr = "[id : "+this.id + " 이름:" + this.name+"]";
		return returnStr;
	}
}

[com.oracle.oBootJpa01.repository]

  • MemberRepository.java( interface )
package com.oracle.oBootJpa01.repository;

import com.oracle.oBootJpa01.domain.Member;

public interface MemberRepository {
	Member		memberSave(Member member);
}

[com.oracle.oBootJpa01.repository]

  • JpaMemberRepository.java( +interface )
package com.oracle.oBootJpa01.repository;

import javax.persistence.EntityManager;

import com.oracle.oBootJpa01.domain.Member;

@Repository
public class JpaMemberRepository implements MemberRepository {
	// DML 작업을 하기 위해서 필수
	private final EntityManager em;
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}
	
	@Override
	public Member memberSave(Member member) {
		// 저장 method
		em.persist(member);
		System.out.println("JpaMemberRepository memberSave member After...");
		return member;
	}

}

[com.oracle.oBootJpa01.service]

  • MemberService.java
    @Transactional 추가
package com.oracle.oBootJpa01.service;

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
public class MemberService {
	private final MemberRepository memberRepository;
	
	@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("memberRepository memberSave After");
		return member.getId();
	}
	
}

📂static

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

[templates.members]

  • createMemberForm.html
<!DOCTYPE html>
<html xml: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>
</body>
</html>

❌ 테이블이 없어서 오류가 난다면?

알아서 테이블까지 생성한다.

👉결과화면

💡트랜잭션

관계형 데이터베이스에서 실행되는 여러개의 sql 명령문을 하나의 논리적 작업 단위로 처리하는 개념
(동시에 커밋하거나 동시에 롤백을 하는 단위)

  1. Atomicity 원자성 : 분해가 불가능한 최소의 단위로 all or nothing
  2. Consistency 일관성 : 성공적으로 완료되면 모순없이 일관된 상태유지
  3. Isolation 고립성 : 생성 중 연산결과는 다른 트랜잭션 접근 불가
  4. Durability 지속성 : 완료된 트랜잭션 결과는 영속적으로 DB에 저장됨

🔎 @Table를 추가하면?

엔티티와 매팅
spring 어노테이션 정리
-> 클래스 이름과 테이블 이름이 다를 경우 JPA에게 테이블 이름을 알려주는 것

  • 엔티티와 매핑할 테이블을 지정
  • 생략 시 매핑한 엔티티를 테이블 이름으로 사용



📝회원조회(JPA)


[com.oracle.oBootJpa01.domain]

  • Member.java

    table을 member1로 지정했기 때문에 알아서 member1 테이블을 찾는다.
    만약 table name을 지정하지 않는다면 클래스 member를 찾아 테이블을 생성한다.

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")
public class Member {
	@Id
	private Long id;
	private String name;
    
    // 객체를 보고 싶을 때?
    @Override
	public String toString() {
		String returnStr = "";
		returnStr = "[id : "+this.id + " 이름:" + this.name+"]";
		return returnStr;
	}
}

[com.oracle.oBootJpa01.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 final MemberService memberService;
	private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
	@Autowired
	public MemberController(MemberService memberService) {
		this.memberService = memberService;
	}
	
	@GetMapping(value = "/members/new")
	public String createFrom() {
		System.out.println("MemberController /members/new start...");
		return "/members/createMemberForm";
	}
	
	@PostMapping(value = "/members/save")
	public String memberSave(Member member) {
		System.out.println("MemberController /members/save 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";
	}
}

[com.oracle.oBootJpa01.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
public class MemberService {
	private final MemberRepository memberRepository;
	
	@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("memberRepository memberSave After");
		return member.getId();
	}

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

[com.oracle.oBootJpa01.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();
}
  • JpaMemberRepository.java

    em.createQuery("select m from Member m", Member.class)
    -> 여기서 Member는 객체다 / 테이블이 아니다

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
public class JpaMemberRepository implements MemberRepository {
	// DML 작업을 하기 위해서 필수
	private final EntityManager em;
	public JpaMemberRepository(EntityManager em) {
		this.em = em;
	}
	
	@Override
	public Member memberSave(Member member) {
		// 저장 method
		em.persist(member);
		System.out.println("JpaMemberRepository memberSave member After...");
		return member;
	}

	@Override
	public List<Member> findAllMember() {
		List<Member> memberList = em.createQuery("select m from member m", Member.class)
									.getResultList();
        System.out.println("JpaMemberRepository findAllMember memberList.size()->"+memberList.size());
		return memberList;
	}

}

[templates.members]

  • memberList.html
<!DOCTYPE html>
<html xml: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>
</body>
</html>


🤔Service에서 트랜잭션을 걸어주는 이유는?

Isolation Level
예를 들어 은행 업무를 한다고 생각해보자.
입금이 되는 순간 출금이 되는 순간이 있을 것이다.
여러가지 작업을 동시에 커밋해야 하는데 그렇기 때문에 service에서 dao결과를 가져와 동시에 커밋을 해준다.
servcie에서 return 하는 순간 commit 된다.
따라서 동시에 커밋하고 롤백하는 과정(DML)은 service에서 담당해야하기 때문에 service에서 transaction 시킨다.

0개의 댓글