Developer Note/국비과정 수업내용 정리&저장

24년 11월 22일

DH_PARK 2024. 11. 25. 22:46

 

Spring - Spring Boot 초기 데이터 설정 (data.sql,schema.sql)

스프링부트에서는 서버를 실행시킬 때 자동으로 테이블을 생성할 수가 있는데

resource 폴더 안에 data.sql 또는 schema.sql 같은 SQL 파일을 사용해서 데이터베이스를 사용하는 방식이 있다.

이런 방식을 왜 사용하는걸까 ?

찾아보니 개발 및 테스트 단계에서 효율성을 높이고 데이터베이스를 자동으로 설정할 수 있도록 지원하는데 유용하다고 한다.

생각해보면 그렇다. 프로젝트를 진행하거나 할 때 반드시 필요한 테이블 정보 같은게 있다면 프로젝트 시작 초기에 테이블을 미리 생성해두는게 편한데 이 때는 이러한 방식을 사용하는게 더 좋은 방식일듯 하다.

사용법

resource 폴더 안에 데이터베이스 초기 설정을 위해 기본적으로 script(schema.sql, data.sql) 파일을 생성한다.

관념적으로 데이터 정의어(DDL)는 schema.sql 파일에 작성하고 데이터 조작어*(DML)*은 data.sql 파일에 작성됩니다.

두 개의 파일을 만들어 사용중.

#schema.sql
CREATE TABLE IF NOT EXISTS A(id int primary key ,password int NOT NULL);
--구조 만들때 사용
IF NOT EXISTS 는 TABLE 뒤에 사용 !
--데이터 넣을 때 사용
insert ignore into a values(1,11);
insert ignore into a values(2,22);

insert ignore into book values (null,'book','p1','a1','111');

이러고 프로젝트를 실행하면 mysql 에 테이블이 생성된다 !

근데 이게 필요없을 수도 있으니 설정을 해줄 수도 있다.

application.properties 파일 안이다.

## JPA INIT
## Spring Boot 애플리케이션이 시작될 때 SQL 초기화 스크립트를 실행하도록 하는 옵션
## always : 항상 / embedded : 내장DB사용시만 / never : SQL스크립트 사용안함 / resource : 클래스패스 상의 schema.sql 및 data.sql과 같은 초기화 스크립트를 실행
#spring.sql.init.mode=always
#
## DataSource 초기화를 지연시키는 옵션 / Spring Boot 애플리케이션이 시작될 때 데이터 소스를 초기화하는 시점을 나중으로 미룹니다.
#spring.jpa.defer-datasource-initialization=true

##DataSource 초기화를 지연시키는 옵션 / Spring Boot 애플리케이션이 시작될 때 데이터 #spring.jpa.defer-datasource-initialization=true

이 부분은 간단히 말하면 DataSource 접근 전인지 후인지 결정하는 옵션이다.

JPA 에서의 트랜잭션 처리

	@Bean(name="dataSourceTransactionManager")
	public DataSourceTransactionManager transactionManager() { //JDBC 기반 트랜잭션 관리를 지원하는 트랜잭션 매니저
		return new DataSourceTransactionManager(dataSource);  //단순한 JDBC 를 사용하여 쿼리를 실행할 때 사용한다.
	}

	// JPA TransactionManager Settings
	@Bean(name="jpaTransactionManager") //JPA 기반 트랜잭션 관리를 지원하는 트랜잭션 매니저
	public JpaTransactionManager jpaTransasctionManager(EntityManagerFactory entityManagerFactory) { //EntityManagerFactory 와 dataSource를 사용하여 트랜잭션을 관리함
		JpaTransactionManager transactionManager = new JpaTransactionManager(); //hibernate 같은 JPA 구현체를 사용하는 프로그램에서 사용됨
		transactionManager.setEntityManagerFactory(entityManagerFactory); //
		transactionManager.setDataSource(dataSource);
		return transactionManager;
	}

여기서도 트랜잭션을 사용한다. (정말 트랜잭션은 sql 을 사용하는 모든곳에서 다 사용하는 것 같다.)

테스트

       //MYBATIS EXCEPTION
        @Transactional(rollbackFor = SQLException.class,transactionManager = "dataSourceTransactionManager")  //SQL 예외가 발생하면 롤백
        void txMapper(){
            log.info("txMapper 시작 ㄱㄱㄱㄱ");
            MemoDto memoDto = new MemoDto(1234,"a","a");    //트랜잭션을 설정해주니 값이 안들어감
            memoMapper.Insert_tx(memoDto);
            memoMapper.Insert_tx(memoDto);
        }
    
    
    
        //JPA EXCEPTION
        @Transactional(rollbackFor = SQLException.class,transactionManager = "jpaTransactionManager")
        void jpaRepository() throws SQLException {
            log.info("jpa리포지토리 시작 ㄱㄱㄱㄱ");

            Book book = new Book(2555L,"a","a","2","a");
            bookRepository.save(book);
            throw new SQLException("SQL 오류임 ");

        }

@Transactional 어노테이션을 붙여주면 트랜잭션 처리를 하게 되는데 ,

이 태그를 붙여주는 이유가 있다고 한다.

 

더티 체킹이라고 하는 상태변경검사를 JPA 에서 진행하게 되는데 (여기선 더티 체크 = 상태변경검사)

더티 체킹은 트랜잭션 안에서 엔티티의 변경이 일어나면 변경 내용을 자동으로 DB 에 반영하는 JPA 특징이다.

그래서 위 테스트 코드처럼 예외가 일어난다면 DB 에 값이 저장되지 않아야 하지만 , 실제로 DB 를확인해보면 값이 저장되어있다.

이 더티 체킹은 transaction 이 commit 될 때 작동한다. 이 때 @Transactional 어노테이션을 사용하면 된다.

이 어노테이션을 붙이면 더티 체킹을 하게 되고 DB 에서 commit 을 해서 save 없이도 수정사항을 반영할 수 있게 된다.

@Transactional

이 어노테이션이 붙은 트랜잭션 안에서 예외가 발생했을 경우 , 만약 그 예외가 런타임 예외일 경우 , 자동적으로 롤백이 발생하지만 다른 예외일 경우 롤백이 되지 않는다.

이 때는 어노테이션 옆에 rollbackFor 옵션을 사용해서 해당 체크 예외를 적어주면 된다.

근데 이걸 보다가 궁금한게 rollback 을 명시하면 롤백이 알아서 되는데 굳이 트랜잭션 매니저 객체를 지정해줘야 하는 이유가 있나? 하는 궁금증이 들었다.

찾아보니 트랜잭션매니저를 지정할 필요가 없는 경우가 있긴하다.

트랜잭션 매니저를 지정할 필요가 없는 경우

  • 애플리케이션에 Transaction Manager 가 하나만 있을 때
  • 일반적인 JPA 기반 애플리케이션

지정해야 하는 경우

  • 여러 트랜잭션 매니저를 사용하는 경우
  • 특정 커스텀 된 트랜잭션 매니저를 무조건 사용하려고 할 때

※ 따라서 내가 트랜잭션 흐름을 제어하려고 할 때는 그냥 Transaction 어노테이션을 사용해서 트랜잭션을 제어하는게 효율적이다 ! (그냥 롤백해야하는 상황있으면 웬만해서 사용해라.


타임리프 (thymleaf)

타임리프란 ?

컨트롤러가 전달하는 데이터를 통해 동적으로 화면을 만들어주는 템플릿 엔진.

사용이유 ?

서버상에서 동작하지 않아도 HTML 파일의 내용을 바로 확인이 가능하다.

순수 HTML 구조를 유지한다.

Maven 으로 가서 받건가 아니면 Spring.start.io 에서 의존성을 추가 한 다음 제너레이트 말고

Explore 으로 의존성을 복사해서 추가한다.

타임리프 의존성 추가

	//타임리프
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

타임리프 설정

# THYMLEAF settings
spring.thymeleaf.prefix:classpath:/templates/
spring.thymeleaf.suffix:.html
spring.htymeleaf.mode:HTML5
spring.thymeleaf.cache:false
spring.thymeleaf.view-names:th/*
  
<html lang="en" xmlns:th="https://www.thymeleaf.org/">  <!-- 타임리프 홈페이지 경로로 하면 최근 버전? 같은걸로 됨 -->

타임리프 사용을 위해서 html 태그에 이와 같이 추가해준다.

문법

대부분의 HTML 속성은 타임리프의 속성으로 쓰이는 th:html 속성으로 변경할 수 있다.

@RequestMapping("/th")
public class ThymeleafTestController {

    @GetMapping("/test1")
    public void t1(Model model){
        log.info("GET /th/test !!!");
        model.addAttribute("name","PDH");   //배열로써 한번에 전달할 수도 있나 ?
        model.addAttribute("memo",new MemoDto(1,"TEXT","WRITER"));

이렇게 request 객체로 전달한 파라미터가 있다.

<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org/">  <!-- 타임리프 홈페이지 경로로 하면 최근 버전? 같은걸로 됨 -->
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>


<h1>hihihi</h1>

NAME: <span th:text="${name}"></span><br> //서버에 전달된 name 값 출력
MEMO : <span th:text="${memo}"></span><br> //memo 값 출력
MEMO.id : <span th:text="${memo.id}"></span><br> //memo 객체를 출력. 개별 속성 접근 가능
MEMO.text : <span th:text="${memo.text}"></span><br>
MEMO.writer : <span th:text="${memo.writer}"></span><br>

<hr>
<th:block th:if="${isAuth}">
  <div>로그인 된 상태</div>
</th:block>
<th:block th:unless="${isAuth}"> <!-- false 일 때 ? -->
  <div>로그아웃 상태</div>
</th:block>

<hr>
<th:block>
  <div th:each="dto:${list}">   <!--list 개수만큼 만들어짐-->
    <span th:text="${dto.id}"></span>
    <span th:text="${dto.text}"></span>   <!-- dto.text 가 getText() 이랑 같은 의미임 -->
    <span th:text="${dto.writer}"></span>
  </div>
</th:block>
<hr>
<a th:href="@{/th/param1(id=${memo.id},text='aaa',writer='bbb')}">ㄱㄱㄱ</a>
<br>
<a th:href="@{/th/param2/{id}/{text}/{writer}(id=${memo.id},text='aaa',writer='bbb')}">이동하기2</a>


<script th:inline="javascript"> <!--이게 무엇 ?--> <!--자료형이 자바스크립트에 맞게 들어온다는데 ?-->

  //Model Attr String Data
  const v1=[[ ${name} ]];  //이건 무슨 문법?

  console.log('v1',v1);   타임리프 데이터를 자바스크립트 코드에 직접 삽입할 수 있다.
                       //이 때 데이터는 JSON 형식으로 변환된다
                       //${} 표현식이 처리되어 자바스크립트 변수에 값을 전달한다.
  const v2 = [[${memo}]];
  console.log('v2',v2);

  const v3 = [[ ${list}]];
  console.log('v3',v3);

  const v4 = [[ ${bookList} ]];
  console.log('v4',v4);

  const v5 = [[ ${list}]];
  console.log('v5',v5);

</script>





</body>
</html>

 

 

 

 

여기서 ${} 을 el 표기법과 헷갈릴 수 있지만 이것은 타임리프의 표현식이다.

<div th:insert="~{th/fragments/header::headerFragment}"></div>

 

이 부분은 th:insert 뒤에 지정된 타임리프 프래그먼트를 현재 위치에 삽입하는 코드이다.

insert는 포함된 태그안에서 HTML 구조가 추가가 된다.

(Replace) 는 포함된 div 태그를 차지해버리고 그 자리를 대체해서 HTML 구조를 생성한다.


'Developer Note > 국비과정 수업내용 정리&저장' 카테고리의 다른 글

24년 11월 26일  (1) 2024.12.05
24년 11월 25일  (0) 2024.11.26
24년 11월 21일  (3) 2024.11.25
24년 11월 20일  (0) 2024.11.25
24년 11월 19일  (1) 2024.11.25