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 |