본문 바로가기

신세계 국비과정/신세계 - Spring 공부

16주차 배운점 - 스프링 부트, ORM, 하이버네이트

728x90
반응형

다오가 xml 파일로감

 

스프링 부트 프레임워크

 

스프링 프레임워크 개발 도구
Enterprise Level Application 개발을 위해 필요한 기능을 제공하는 개발도구

 

특징

 

Auto Configuration(자동 설정)
Embedding Tomcat (내장 서버)
단독 실행 가능한 도구

 

스프링 컨테이너(Spring Container)

 

스프링 안에서 동작하는 빈 생성 및 관리 주체

 

빈(Bean)

 

스프링 컨테이너가 생성하고 관리하는 객체
빈 등록 방법

 

관점 지향 프로그래밍(AOP)

 

AOP = Aspect Oriented Programming
핵심 관점, 부가 관점으로 나누어 프로그래밍하는 것

 

핵심 기능 => 로그인 서비스, 판매 서비스, 다운로드
부가기능 =>
로깅, 트랜잭션, 보안 등 서비스에 꼭 필요하지만 핵심 기능이 아닌 애플리케이션의 전 영역에서 나타날 수 있는 기능을 말한다. (횡단 관심사)

 

새로운 기술이 아니라, 스프링프레임워크를 개선한 것
대표적 개선사항

  • 개발 환경 설정 간소화
    스프링은 버전에 따라 동작하는 외부라이브러리를 찾아 연동해야 하지만
    스프링부트는 미리 설정된 스타터프로젝트로 외부 라이브러리를 최적화해 제공하여 사용자가 직접 연동할
    필요가 없다.
  • 웹 애플리케이션 서버를 내장
    내부에 웹어플리케이션 서버(WAS)인 톰캣을 가지고 있어 웹서비스를 jar파일로 간편하게 배포할 수 있어,
    개발자가 개발에 더 집중할 수 있도록 지원

 

스프링 부트 3 구조

 

각 계층은 서로 영향을 주지 않고 각자 책임에 맞는 역할을 함, 독립적!

 

프레젠테이션 계층
HTTP 요청을 받아 비즈니스 계층으로 전송
컨트롤러 : 프레젠테이션 계층을 실제 구현한 것

 

비즈니스 계층
비즈니스 로직을 처리(통상적인 앱 기능)
서비스: 비즈니스 계층을 실제 구현한 것

 

퍼시스턴스 계층
데이터베이스 관련 로직 처리
리포지토리 : 퍼시스턴스 계층을 실제 구현한 것

 

 

 

동적 컨텐츠 처리 구조

 

  1. 컨트롤러를 정의한 후 사용자가 localhost:8080/hello 페이지를 요청
  2. 내장 톰캣 서버에서 스프링 컨테이너에 hello 페이지 관련 Controller를 요청
  3. 만약 Controller가 있을 경우 해당 Controller가 요청을 받아(@GetMapping("hello"))
  4. model을 가공(model.addAttribute)하고, HTML 파일 경로를 viewResolver에 전달(return "hello")
  5. viewResolver가 resources/templates 폴더에서 전달받은 model을 가공하여 HTML 파일을 변환시키고, 사용자의 브라우저로 넘겨준다.

 

 

 

정적 컨텐츠 처리 구조

 

  1. 사용자가 localhost:8080/hello-static.html 페이지를 요청
  2. 내장 톰캣 서버에서 해당 페이지를 처리할 컨트롤러를 찾음
  3. 아무런 컨트롤러도 정의되지 않으므로, 내장 톰캣 서버가 resources/static 폴더에서
    사용자가 요청한 페이지와 일치하는 HTML 파일을 찾아서 보여준다.

 

 

DataSource 설정

 

자동 설정기능으로 인해 의존성 라이브러리를 추가한 것 만으로 자동으로 관련 설정


• HikariCP를 기본으로 사용
• Spring Data JPA에서 사용할 데이터베이스 관련 설정 필요
• 설정 파일
application.properties 혹은 application.yml 형식

 

 

application.properties

 

서버관련 설정

server.port=8090
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url = jdbc:mariadb://localhost:3307/webdb
spring.datasource.username = webuser 
spring.datasource.password = webuser

logging.level.org.springframework=info
logging.level.com.ssg=debug

spring.jpa.hibernate.ddl-auto=none
#내눈으로보고싶다!
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true

 

 

 

스프링부트에서의 웹 개발

 

web.xml이나 servlet-context.xml이 없는 환경에서 개발
설정을 위한 @Configuration이나 상속 등을 사용
스프링부트는 기본적으로 JSP를 지원하지 않음

 

hello 예제
컨트롤러: 모델에 msg 로 문자열 보냄

 

@Controller
@Log4j2
public class SampleController {
    @GetMapping("/hello")
    public void hello(Model model){
        log.info("hello~~");
        model.addAttribute("msg","Hello Spring boot!");
    }
}

 

html:
msg 에 담긴게 출력됨

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
</body>
</html>

 

 

 

 

Rest방식 컨트롤러

 

@RestController
@Log4j2
public class SampleJSONController {
    @GetMapping("/helloArr")
    public String[] hello(){
        log.info("hello~~");

        return new String[]{"Hello","Springboot","nice meet you"};
    }
}

 

데이터만 보냄 ,요걸 자바스크립트로 받아서 처리

 

 

 

Thymeleaf

 

서버사이드 템플릿 : jsp처럼 직접 데이터를 생성하지 않는다

 

JSP의 경우 서블릿으로 변환된 후에 실행되는 방식
Thymeleaf는 서버사이드 템플릿 엔진
HTML의 구조에 추가적인 태그없이 선언적으로 데이터 바인딩 처리

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> 
  // 타임리프 이용하겠다 // 네임스페이스: th
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
</body>
</html>

 

 

th로 사용할때 해당변수 없다고 에러 뜸, 에러 끄기

 

 

 

Model에 리스트 보내기

 

@RestController에선 안됨

 

@Controller

 

@GetMapping("/ex/ex1")
    public void ex1(Model model){
        List<String> list = Arrays.asList("AAA","BBB","CCC","DDD");
        model.addAttribute("list", list);
    }

 

 

html

  1. 그냥 리스트 출력
  2. 리스트 하나씩 출력
  3. \
  4. 로 하나씩 출력
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h4>[[${list}]]</h4>
<hr/>
<h4 th:text="${list}"></h4>
<hr/>
<ul>
    <li th:each="str : ${list}" th:text="${str}"></li>
</ul>
<ul>
    <th:block th:each="str: ${list}">
        <li>[[${str}]]</li>
    </th:block>
</ul>
</body>
</html>

 

 

 

 

링크 처리

 

절대 경로/컨텍스트 경로를 자동으로 처리
‘@’를 이용해서 처리
쿼리 스트링 처리

 

a 태그 값 보내기

 

<div>
    <a th:href="@{/hello(name='AAA', age=20)}">Go to /hello </a>
</div>

 

 

누르면 name age 전달

 

 

인라인 처리


JavaScript의 경우 변수를 자동으로 JavaScript Object 형태로 출력

SampleDTO클래스

 

 class SampleDTO{
        private String p1,p2,p3;

        public String getP1() {
            return p1;
        }

        public String getP2() {
            return p2;
        }

        public String getP3() {
            return p3;
        }
    }

 

 

controller

 

@GetMapping("/ex/ex2")
    public void ex2(Model model){
        log.info("ex/ex2...");
        List<String> list = IntStream.range(1,10).mapToObj(i->"Data "+i).collect(Collectors.toList());

        model.addAttribute("list",list);

        Map<String,String> map = new HashMap<>();
        map.put("A","aaaa");
        map.put("B","bbbb");

        model.addAttribute("map",map);

        SampleDTO sampleDTO = new SampleDTO();
        sampleDTO.p1 = "Value--p1";
        sampleDTO.p2 = "Value--p1";
        sampleDTO.p3 = "Value--p1";

        model.addAttribute("dto",sampleDTO);

    }

 

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div th:text="${list}"></div>
<div th:text="${map}"></div>
<div th:text="${dto}"></div>
<script th:inline="javascript">
    const list = [[${list}]]
    const map = [[${map}]]
    const dto = [[${dto}]]
    console.log(list)
    console.log(map)
    console.log(dto)
</script>
</body>
</html>

 

 

 

 

Thymeleaf의 레이아웃 기능

 

layout - layout1.html

 

<!DOCTYPE html>
<html
        xmlns:layout="http://www.ultraq.net.nz/thymeleaf/
        layout"
        xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Layout page</title>
</head>
<body>
<div>
    <h3>Sample Layout Header</h3>
</div>
<div layout:fragment="content">
    <p>Page content goes here</p>
</div>
<div>
    <h3>Sample Layout Footer</h3>
</div>
<th:block layout:fragment="script" >
</th:block>
</body>
</html>

 

 

controller

 

 @GetMapping("/ex/ex3")
    public void ex3(Model model){
        model.addAttribute("arr",new String[]{"aaa","bbb","ccc"});

    }

 

 

ex3.html

 

<!DOCTYPE html>
<html
        xmlns:layout="http://www.ultraq.net.nz/thymeleaf/
        layout"
        xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{layout/layout1.html}">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div layout:fragment="content">
    <h1>ex3.html springboot test</h1>
</div>
</body>

<script layout:fragment="script" th:inline="javascript">
    const arr = [[${arr}]]
</script>

 

 

 

 

ORM

 

Object Relational Mapping

 

자바의 객체와 데이터베이스를 연결하는 프로그래밍 기법
ORM이 없다면 데이터베이스를 조작하기 위한 언어(SQL)를 새로 배워야 함
ORM이 있으면 스프링 부트에서 사용하는 자바 언어로 데이터베이스를 조작할 수 있음

 

 

 

JPA와 하이버네이트

 

ORM의 종류는 매우 다양
자바는 JPA를 표준으로 사용
JPA = java persistence API
자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스
실제 사용을 위해서는 추가로 ORM 프레임워크를 선택해야 함
ORM 프레임워크, 하이버네이트
JPA 인터페이스를 구현한 구현체 = 이것을 실제 코드에 사용

 

JPA 장점
1. 특정 데이터베이스에 종속되지 않음
2. 객체지향적으로 설계 가능
3. 유지보수 유리 / 생산성 향상

 

JPA 단점
1. 복잡한 쿼리 처리
2. 성능 저하 위험
3. 익히는 시간

JPA 동작 방식

 

JPA와 하이버네이트

 

영속성 컨텍스트
엔티티를 관리하는 가상의 공간
데이터베이스에서 효과적으로 데이터를 가져온다
• 1차 캐시
데이터베이스를 직접 거치지 않고 빠르게 데이터를 조회
• 쓰기 지연
트랜잭션을 커밋하기 전까지 쿼리를 모았다가 보내 실행
• 변경 감지
1차 캐시에 저장되어 있는 엔티티와 현재 엔티티 비교하여 변경 사항을 감지
• 지연 로딩
쿼리로 요청한 데이터를 즉시 로딩하지 않음 자원 효율을 고려하여 데이터 조회

 

 

엔티티 상태 4가지


• 비영속 상태
엔티티 매니저가 엔티티를 관리하지 않는 상태
• 관리 상태
엔티티가 관리되는 상태
• 분리 상태
엔티티가 분리된 상태
• 삭제 상태
엔티티가 삭제된 상태

 

 

JPA 동작 방식


• 엔티티
데이터베이스의 테이블에 대응하는 클래스. @Entity 어노테이션을 붙여서 관리
• 엔티티 매니저 팩토리 : 엔티티 매니저 인스턴스를 관리하는 주체
엔티티 매니저
영속성 컨텍스트에 접근하여 엔티티에 대한 데이터베이스 작업 제공

 

 

 

엔티티 생명주기

 

 

 

Board 엔티티

 

 

BaseEntity 상속받음

 

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Board extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long tno;

    @Column(length = 500, nullable = false)
    private String title;
    
    @Column(length = 2000, nullable = false)
    private String content;
    
    @Column(length = 50, nullable = false)
    private String writer;
}

 

 

BaseEntity : 기준

 

@MappedSuperclass
// 엔티티의 공통속성을 처리 : 모든 테이블에서 데이터가 추가된 시간이나, 수정된 시간을 컬럼으로 작성
@EntityListeners(value = {AuditingEntityListener.class})
@Getter
public class BaseEntity {
    @CreatedDate
    @Column(name = "regdate",updatable = false)
    private LocalDateTime regDate;

    @LastModifiedBy
    @Column(name="moddate")
    private LocalDateTime modDate;


}

 

 

main

 

// 스스로 스타트할수있는 독립된 실행 도구
@SpringBootApplication
@EnableJpaAuditing // 등록해줘야함 / Auditing 어노테이션 활성화시키는 거
public class Sb01Application {

    public static void main(String[] args) {
        SpringApplication.run(Sb01Application.class, args);
    }
}

 

 

Repository

 

JpaRepository 인터페이스를 상속하는 인터페이스인 BoardRepository 선언만으로
CRUD와 페이징 처리 완료

 

public interface BoardRepository extends JpaRepository<Board, Long> {
// entity, id 타입
}

 

 

Test


테이블만들고 값넣기

 

@SpringBootTest
@Log4j2
public class BoardRepositoryTests {

    @Autowired
    private BoardRepository boardRepository;
    //save() 통해서 데이터베이스 insert 기능 수행

    @Test
    public void testInsert(){
        IntStream.rangeClosed(1,100).forEach(i->{
            Board board = Board.builder()
                    .title("title"+i)
                    .content("content..." + i)
                    .writer("user"+(i%10))
                    .build();
            Board result = boardRepository.save(board);
            log.info("BNO: " + result.getTno());
        });


    }
}

 

 

 

 

한개 가져오기 설렉트

 

 @Test
    public void testSelect(){
        Long tno = 100L;
        Optional<Board> result = boardRepository.findById(tno);

        Board board = result.orElseThrow();
        log.info(board);

    }

 

 

 

 

board에 추가

 

값 바꾸는 메소드

 

public void change(String title, String content){
        this.title = title;
        this.content = content;
    }

 

 

업데이트


board 값바꾼거로 저장

 

 @Test
    public void testUpdate(){
        Long tno = 100L;
        Optional<Board> result = boardRepository.findById(tno);
        Board board = result.orElseThrow();
        board.change("update title 100", "update content 100");
        boardRepository.save(board);
    }

 

 

 

 

delete 테스트

 

@Test
    public void testDelete(){
        Long tno = 100L;
        boardRepository.deleteById(tno);

        
    }

 

 

JPA에서 수정이나 삭제를 수행하기 전에 왜 먼저 SELECT 문을 실행하는지 궁금하신 것 같네요. 이러한 동작은 JPA의 작동 원리와 엔티티의 생명주기 관리에 깊이 연관되어 있습니다. 몇 가지 주요 이유를 설명드리겠습니다.
1. 엔티티 상태 관리
JPA는 엔티티의 생명주기를 관리합니다. 엔티티 매니저는 엔티티가 영속성 컨텍스트에 들어온 순간부터 해당 엔티티의 상태 변화를 추적합니다. 수정이나 삭제 작업을 수행하기 위해서는 먼저 해당 엔티티를 영속 상태로 만들어야 합니다. 영속 상태의 엔티티는 영속성 컨텍스트에서 관리되며, 이를 위해 JPA는 먼저 데이터베이스에서 엔티티를 조회(SELECT)하여 영속성 컨텍스트에 영속 상태로 등록합니다.
2. 데이터 일관성 및 정확성
SELECT 문을 먼저 실행함으로써, JPA는 데이터베이스의 현재 상태를 정확하게 반영한 엔티티를 메모리 상에 로드합니다. 이는 작업 중인 엔티티가 최신 상태임을 보장하며, 이후의 수정이나 삭제가 정확하게 수행될 수 있도록 합니다. 데이터의 일관성과 정확성을 유지하는 것은 특히 동시성이나 복잡한 비즈니스 로직을 다룰 때 중요합니다.

 

 

JPA 수정이나 삭제시에 굳이 select 문이 먼저 실행되는 이유는?

 

JPA를 이용하는 것은 영속컨텍스트와 데이터베이스를 동기화하여 관리한다는 의미이다.
특정한 엔티티 객체가 추가되면 영속 컨텍스트에 ㅊ가되고 데이터베이스와 동기화가 이루어진다.
수정이나 삭제시 영속 컨텍스트에 해당하는 엔티티 객체가 존재해야하므로 먼저, Select로 엔티티 객체를 여속 컨텍스트에 저장하고 이를 삭제한 후 실제 delete가 이루어진다.

 

Pageable 와 Page<E>

 

페이징 처리는 Pagable 타입의 객체를 파라미터로 전단하여 구현한다.
PageRequest.of() 기능을 이용하여 개발한다.

 

 @Test
    public void testPaging(){
        Pageable pageable = PageRequest.of(0,10, Sort.by("tno").descending());
        Page<Board> result = boardRepository.findAll(pageable);

        log.info("total count :"+ result.getTotalElements());
        log.info("total page :"+ result.getTotalPages());
        log.info("page number :"+ result.getNumber());
        log.info("page size :"+ result.getSize());

        //List<Board> todoList = result.getContent();
    }

 

 

 
반응형