네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'web/Spring'에 해당되는 글 67건


반응형

hibernate.hbm2ddl.auto 속성

 

데이터베이스 스키마 자동 생성

- DDL을 애플리케이션 실행 시점에 자동 생성한다.

- 테이블 중신에서 객체 중심으로 이동

- 데이터베이스 방언을 활용하여 데이터베이스에 맞는 적절한 DDL 생성한다.

- 이렇게 생성된 DDL은 개발 단계에서만 사용 (운영서버에는 적절히 다듬은 후 사용한다.)

 

1. 속성

persistence.xml의 속성 옵션이다.

옵션 설명
create 기존 테이블 삭제 후 다시 생성 DROP + CREATE
create-drop CREATE와 같지만 종료 시점에 DROP
update 변경 분만 반영된다. (추가만 되고 지워지는 것은 되지 않음)
validate 엔티티와 테이블이 정상 매핑 되었는지만 확인
none 사용하지 않음

 

2. 주의점

- 운영장비에는 절대 create, create-drop, update를 사용하면 안된다. (데이터가 없어질 위험이 있음)

- 개발 초기 단계는 create 또는 update

- 테스트 서버는 update 또는 validate

- 운영 서버는 validate 또는 none

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

- Spring Data JPA

- Spring Boot

 

 

application.yml

spring:
  datasource:
    hikari:
      bootdb1:
        driver-class-name: [driver-name]
        jdbc-url: [url]
        username: [username]
        password: [password]
      bootdb2:
        driver-class-name: [driver-name]
        jdbc-url: [url]
        username: [username]
        password: [password]

 

* 정의해야 하는 것

- DataSource

- EntityManagerFactory

- TransactionManager

=> DBConfig를 만들어주자.

 

LegacyDBConfig.java

package com.jiyoon.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "legacyEntityManager",
        transactionManagerRef = "legacyTransactionManager",
        basePackages = "com.jiyoon.repository.legacy"
)
public class LegacyDBConfig { 

    @Bean
    @ConfigurationProperties("spring.datasource.hikari.bootdb2")
    public DataSource legacyDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public PlatformTransactionManager legacyTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(legacyEntityManager().getObject());

        return transactionManager;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean legacyEntityManager() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(legacyDataSource());
        em.setPackagesToScan("com.jiyoon.model.legacy");

        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(adapter);
        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
        properties.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
        em.setJpaPropertyMap(properties);

        return em;
    }
}

 

TargetDBConfig.java

package com.jiyoon.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "targetEntityManager",
        transactionManagerRef = "targetTransactionManager",
        basePackages = "com.jiyoon.repository.target"
)
public class TargetDBConfig {

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.hikari.bootdb1")
    public DataSource targetDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public PlatformTransactionManager targetTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(targetEntityManager().getObject());

        return transactionManager;
    }

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean targetEntityManager() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(targetDataSource());
        em.setPackagesToScan("com.jiyoon.model.target");

        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(adapter);
        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
        properties.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
        em.setJpaPropertyMap(properties);

        return em;
    }
}

 

@ConfigurationProperties 과 관련 글

https://frogand.tistory.com/131

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

 

Core Features

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an

docs.spring.io

 

* 트랜잭션

여러 단계를 수행할 때, 하나라도 실패하면 모두 취소되어야 하며 데이터의 무결성을 보장한다.

 

* 스프링의 트랜잭션 지원

선언적 트랜잭션을 지원한다. 트랜잭션의 범위를 코드 수준으로 정의 가능하며 설정 파일 또는 어노테이션을 이용하여 규칙 및 범위를 설정할 수 있다.

 

- PlatformTransactionManager

트랜잭션은 PlatformTransactionManager 인터페이스를 이용해 추상화했다. DB 연동 기술에 따라 각각의 구현 클래스가 제공된다.

실제 트랜잭션을 처리할 때 PlatformTransactionManager를 사용하진 않는다. 선언적 트랜잭션 방식으로 처리한다.

- JDBC 기반의 트랜잭션 설정

JDBC, MyBatis 등의 JDBC를 이용하는 경우 DataSourceTransactionManager를 관리자로 등록한다.

dataSource 프로퍼티를 통해 전달받은 Connection으로 commit, rollback을 수행하면서 관리한다.

- JPA 트랜잭션 설정

JPA를 사용하는 경우 JpaTransactionManager를 사용한다.

 

 

엔티티

package com.jiyoon.model.legacy

@Entity
@Table(schema = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    private String name;
}

 

JPA 레포지토리

package com.jiyoon.repository.legacy;

public interface UserRepository extends JpaRepository<User, Integer> {}

 

 

출처

https://www.baeldung.com/spring-data-jpa-multiple-databases

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

@ConfigurationProperties

Spring Boot에서 *.properties나 *.yml 파일에 있는 property를 자바 클래스에 값을 바인딩하여 사용할 수 있게 해주는 어노테이션

 

Spring Boot에서는 운영에 필요한 정보를 프로퍼티 파일에서 Key-Value 형태로 저장하여 관리한다.

 

다음과 같은 properties 파일이 있다고 가정할 때 @Value를 사용하여 바인딩할 수 있다.

site-url.naver=https://www.naver.com
site-url.google=https://www.google.com
@Value("${site-url.naver}")
private String naver;

@Value("${site-url.google}")
private String google;

 

@ConfigurationProperties의 좋은점은

프로퍼티를 바인딩할 때 완화된 규칙을 적용하여 다음과 같은 변형도 모두 같게 취급한다.

mail.hostName    // CamelCase
mail.hostname
mail.host-name   // *.properties와 *.yml에 권장되는 표기 방법
mail.host_name   
mail.HOST_NAME   // 시스템 환경변수를 사용할 때 권장

 

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import lombok.Data;

@Component
@ConfigurationProperties(prefix = "site-url")
@Data
public class siteUrlProperties {
	private String naver;
	private String google;
	
}

@Component로 bean을 등록하고

@ConfigurationProperties에 prefix를 선정한다. 

properties 파일에 site-url.*에 대해 바인딩한다.

 

Spring Boot 2.2

스프링 부트 2.2에서는 @ConfigurationProperties 클래스들을 모두 찾아서 등록해주므로 @Component, 혹은 @Comfiguration 과 같은 어노테이션이나 @EnableConfigurationProperties를 붙일 필요가 없다.

 

참고

https://www.baeldung.com/configuration-properties-in-spring-boot

https://sgc109.github.io/2020/07/07/spring-boot-configuration-properties/

 

Spring Boot 의 @ConfigurationProperties

@ConfigurationProperties 는 Spring Boot 에서 properties 파일에 정의된 프로퍼티 중 주어진 prefix 를 가지는 프로퍼티들을 POJO 에 매핑하여 Bean 으로 만들수 있게 해주는 어노테이션이다. 그럼 @ConfigurationPrope

sgc109.github.io

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

타임리프(Thymeleaf) 템플릿에서는 for문, while문 등과 유사한 반복 처리(iteration) 처리를 위해 th:each를 사용한다.

루프 처리중 상태를 추적하는 status 변수를 이용하여 index, count 등을 얻을 수 있다.

 

loop를 원칙적으로 break 하거나 다른 곳에서 사용하는 것이 불가하다는 제한사항이 있지만 Thymeleaf가 View Template Engine이기 때문에 비즈니스 로직을 view에 작성하는 것을 지양해야한다.

 

th:each

반복하는 html 엘리먼트에 th:each 속성을 사용하여 Collection을 반복하여 화면을 처리한다.

<table class="tb_col"> 
    <thead>
    <th>seq</th>
    <th>name</th>
    <th>price</th>
    <th>quantity</th>
    </thead>
    <tbody>
        <tr th:each="product : ${productList}">
            <td th:text="${product.seq}"></td>
            <td th:text="${product.name}"></td>
            <td th:text="${product.price}"></td>
            <td th:text="${product.quantity}"></td>
        </tr>
    </tbody>
</table>

 

#numbers.sequence

컬렉션 없이 단순 반복 처리를 하고 싶다면 Number(org.thymeleaf.expression.Numbers) 클래스의 utility 메서드인 #numbers.sequence을 사용하여 먼저 원하는 반복 횟수만큼의 배열을 생성하고 th:each의 컬렉션에 넣으면 된다.

<tr:block th:each="num : ${#numbers.sequence(1,5)}"> 
    <td th:text="${num}"></td> 
</tr:block>

 

반복 상태 변수 (status)

Thymeleaf에서 th:each를 사용하면 반복 상태를 추적할 수 있는 status 변수를 제공해준다. 이를 이용하여 index, count 등 값을 추출할 수 있다.

- index      현재 반복 인덱스 (0부터 시작)

- count      현재 반복 인덱스 (1부터 시작)

- size         총 요소 수

- current   현재 요소

- even        현재 반복이 짝수인지 여부 (boolean)

- add         현재 반복이 홀수인지 여부 (boolean)

- first         현재 반복이 첫번째인지 여부 (boolean)

- last          현재 반복이 마지막인지 여부 (boolean)

 

status 변수는 기본적으로 오브젝트명 + "Stat" 변수명으로 접근할 수 있으며 th:each 선언시 개발자가 직접 명명하여 사용할 수 있다. 

<div th:each="num : ${#numbers.sequence(1,3)}"> 
    <p th:text="${'index : ' + numStat.index}"></p>
    <p th:text="${'count : ' + numStat.count}"></p> 
    <p th:text="${'size : ' + numStat.size}"></p> 
    <p th:text="${'current : ' + numStat.current}"></p> 
    <p th:text="${'even : ' + numStat.even}"></p> 
    <p th:text="${'odd : ' + numStat.odd}"></p> 
    <p th:text="${'first : ' + numStat.first}"></p> 
    <p th:text="${'last : ' + numStat.last}"></p> 
</div>

 

참고

https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#using-theach

 

Tutorial: Using Thymeleaf

1 Introducing Thymeleaf 1.1 What is Thymeleaf? Thymeleaf is a Java library. It is an XML/XHTML/HTML5 template engine able to apply a set of transformations to template files in order to display data and/or text produced by your applications. It is better s

www.thymeleaf.org

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

[Spring] Apache POI 를 이용한 엑셀 파일 읽기

✨ 의존성

- Spring Boot

- Spring Web

- Thymeleaf

- Lombok

 

1. Apache POI , Tika 관련 의존성 추가

maven일 경우 pom.xml

<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi</artifactId>
  <version>4.1.2</version>
</dependency>
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>4.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tika/tika-core -->
<dependency>
  <groupId>org.apache.tika</groupId>
  <artifactId>tika-core</artifactId>
  <version>2.3.0</version>
</dependency>

 

 

gradle일 경우 build.gradle

// https://mvnrepository.com/artifact/org.apache.poi/poi
implementation group: 'org.apache.poi', name: 'poi', version: '4.1.2'
implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '4.1.2'
// https://mvnrepository.com/artifact/org.apache.tika/tika-core
implementation group: 'org.apache.tika', name: 'tika-core', version: '2.3.0'

spring boot라면 version 입력을 하지 않아도 될 것이다.

 

2. 파일 입력 폼

resources/templates/index.html

<!DOCTYPE HTML>
<html lang="ko" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>

<body>
<form th:action="@{/excel/read}" method="POST" enctype="multipart/form-data">
    <input type="file" th:name="file1">
    <input th:type="submit" value="제출" />
</form>
</body>
</html>

 

3. 객체

ExcelData.java

package model;

import lombok.*;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class ExcelData {

    private int num;
    private String name;
}

 

4. 컨트롤러

ExcelDataController.java

import model.ExcelData;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

@Controller
public class ExcelDataController {

    @PostMapping("/excel/read")
    public String readExcel(@RequestParam("file") MultipartFile file, Model model) throws TikaException, IOException { // 2

        List<ExcelData> dataList = new ArrayList<>();

        try (InputStream is = file.getInputStream();) {

            Tika tika = new Tika();
            String mimeType = tika.detect(is);
            if (isAllowedMIMEType(mimeType)) {
                Workbook workbook = new XSSFWorkbook(file.getInputStream());

                Sheet worksheet = workbook.getSheetAt(0);

                String atchFileId = null;

                for (int i = 1; i < worksheet.getPhysicalNumberOfRows(); i++) { // 1번째 행부터 끝까지
                    Row row = worksheet.getRow(i);
                    
                    ExcelData data = new ExcelData();
                    data.setNum((int) row.getCell(0).getNyumericCellValue());
                    data.setName(row.getCell(1).getStringCellValue());

                    dataList.add(data);
                }

                model.addAttribute("list", dataList);
            } else {
                throw new IOException();
            }
        } catch (Exception e) {
            throw new TikaException("ERROR");
        }

        return "list";
    }


    private boolean isAllowedMIMEType(String mimeType) {
        if (mimeType.equals("application/x-tika-ooxml"))
            return true;
        return false;
    }
}

MultipartFile

Spring 환경이라면 Spring에서 제공하고 있는 MultipartFile 클래스와 MultipartHttpServletRequest 클래스를 사용해서 File 업로드 기능을 구현할 수 있다. 클라이언트에서 서버로 HTTP 요청을 할 때, Content-Type 필드의 내용을 multipart/form-data로 요청한다.

@RequestParam 어노테이션과 함께 MultipartFile 타입을 사용한다. 

 

Apache Tika 라이브러리

Apache Tika 를 사용하여 MIME TYPE를 체크하였다.

 

5. 리스트 화면 

resources/templates/list.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
</head>
<body>
  <table class="table table-striped">
    <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">번호</th>
      <th scope="col">이름</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="data : ${datas}" >
      <td th:text="${dataStat.index}"></td>
      <td th:text="${data.num}"></td>
      <td th:text="${data.name}"></td>
    </tr>
    </tbody>
  </table>
</body>
</html>

Stat을 사용해서 상태변수 (index)를 접근하였다.

 

참고

https://stackoverflow.com/questions/50849800/how-to-read-excel-file-using-spring-boot

https://caileb.tistory.com/152

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !