네로개발일기

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

'web'에 해당되는 글 82건


반응형

엔티티에서 Boolean형을 Database에서는 'Y' 또는 'N'으로 Char(1) 형으로 처리하고 싶을 때 @Converter 어노테이션으 사용하여 해결한다.

1. Converter 객체 생성

BooleanToYNConverter.java

@Converter
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
    
    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        return (attribute != null&& attribute) ? "Y" : "N";
    }
    
    @Override
    public Boolean convertToEntityAttribute(String yn) {
        return "Y".equalsIgnoreCase(yn);
    }
}


* AttributeConverter 인터페이스

public interface AttributeConverter<X, Y> {
    
    Y convertToDatabaseColumn(X var1);
    
    X convertToEntityAttribute(Y var1);
}


2. Entity에 Converter 적용

적용하고 싶은 필드에 @Convert 어노테이션을 붙인다.

@Entity
@Table
@Getter
public class Member {

    // ... 중략 ...
    
    @Covert(converter = BooleanToYNConverter.class)
    private boolean isActive;
}








728x90
반응형
blog image

Written by ner.o

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

반응형

[Thymeleaf 의 String API]

String 객체의 유틸리티 메서드는 #strings 이다.

 

문자열 비교하는 메서드

${#strings.equals(str1, str2)}
$(#strings.equalsIgnoreCase(str1, str2)}

Null-safe String 변환 메서드

${#strings.toString(obj)}

- 배열(array), 리스트(list), 셋(set)에도 잘 동작한다.

 

값(value)이 존재하는지 검사하는 메서드

${#strings.isEmpty(str)}
${#strings.arrayIsEmpty(arr)}
${#strings.listIsEmpty(list)}
${#strings.setIsEmpty(set)}

- true 또는 false로 값을 반환한다.

 

디폴트 값 설정

 

${#strings.defaultString(text,default)}
${#strings.arrayDefaultString(textArr,default)}
${#strings.listDefaultString(textList,default)}
${#strings.setDefaultString(textSet,default)}

 

 

출처

https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#strings

 

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

https://cizz3007.github.io/%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84/syntax/thymeleaf/2018/04/10/thymeleaf2/

 

thymeleaf 문법

Thymeleaf 문법 2

cizz3007.github.io

 

728x90
반응형
blog image

Written by ner.o

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

반응형

1. 설정 값 분리의 필요성

개발을 진행하다 보면 데이터베이스 연결 정보나 외부 API 주소 등과 같은 메타 정보들을 관리해야 한다. 해당 메타 정보들을 클래스 파일에 넣을 수도 있겠지만 다음과 같은 이유로 프로퍼티 파일(properties)나 야믈파일(yml)로 분리해서 관리할 것을 권장한다. 

 

1. 환경에 따라 유연하게 값을 설정할 수 있음.

- 일반적으로 로컬 환경이나 알파/스테이지 환경, 운영환경에 따라 서로 다른 데이터베이스 서버를 가지고 있다. 만약 환경에 따라 다른 값을 자바 코드만으로 설정해주려면 상당히 번거롭고 중복되는 코드가 상당히 많아진다. 그래서 설정값은 별도의 파일로 분리하고, 자바 코드에서는 환경 정보에 맞는 설정값을 불러오도록 한다.

2. 초기값을 설정할 수 있음.

불러오는 설정 값에 초기값을 지정해줄 수 있는데, 이를 통해 설정한 값이 불러지지 않은 경우에 대비할 수 있으며 테스트 작성을 용이하게 할 수 있다. 즉, 초기값을 설정함으로써 테스트 작성에 유연하고 안전하게 개발할 수 있다.

3. 불필요한 컴파일을 줄일 수 있음.

만약 프로퍼티를 분리하지 않는다면 값이 클래스 안에 있을 것이다. 그럴 때 만약 값이 수정되면 소스코드가 변하는 것이므로 다시 컴파일을 진행해주어야 한다. 하지만 설정 파일을 분리하고 @Value로 참조하게 한다면 컴파일없이 주입되는 값을 변경할 수 있다.

 

2. @Value의 사용법과 처리 및 동작 과정

[@Value의 사용법]

@Value로 메타정보를 가져오는 방법으로는 PropertyPlaceHolderConfigurer를 통해 ${}을 사용하는 방식과 SpEL을 통해 #{}을 이용하여 가져오는 방식, 크게 2가지가 있다.

 

1. PropertyPlaceHolderConfigurer를 통한 수동 변환

PropertyPlaceHolderConfigurer은 빈 팩토리 후처리기로써 매번 빈 오브젝트가 만들어진 직후에 오브젝트의 내용이나 오브젝트 자체를 변경하는 빈 후처리기와 달리 빈 설정 메타정보가 모두 준비됐을 때 빈 메타정보 자체를 조작하기 위해 사용된다.

예를 들어 database.username과 같이 properties에 작성된 키 값을 ${}안에 넣어주면 Spring이 PropertyPlaceHolderConfigurer를 통해 초기화 작업 중에 해당 값을 실제 값으로 치환한다.

 

database.username= jiyoon
database.password= password
@Value("${database.username}")
private String username;

@Value("${database.password}")
private String password;

하지만 이러한 방법은 대체할 위치를 치환자로 지정해두고, 별도의 후처리기가 값을 변경해주기를 기대하기 때문에 수동적이다. 그래서 초기 빈 메타정보에는 ${database.username}과 같은 문자열이 등록되어 있다. 그러다가 스프링 컨테이너가 설정 파일에서 대체할 적절한 키 값을 찾지 못하면 해당 문자열이 그대로 남아있게 된다. 그래서 치환자의 값이 변경되지 않더라도 예외가 발생하지 않으므로 SpEL을 통한 능동 변환을 권장한다. 초기에는 PropertyPlaceHolderConfigurer를 사용했어야 했지만 Spring 3부터 SpEL을 지원하면서 이를 이용하는 방식이 권장되고 있다. 

물론 최근 Spring의 버전에선 이런 문제를 개선한 PropertySourcesPlaceholderConfigurer가 등장하면서 적절한 값을 찾지 못하면 에러가 발생하도록 수정되었지만 그래도 수동적인 방법보다는 빈에서 값을 꺼내는 SpEL이 권장된다.

 

2. SpEL을 통한 능동 변환

SpEL은 스프링 3.0에서 처음 소개된 스프링 전용 표현식 언어로 이를 통해 다른 빈 오브젝트나 프로퍼티에 손쉽게 접근할 수 있다. SpEL은 기본적으로 #{}안에 표현식을 넣도록 되어있는데, user.name이라는 표현식은 이름이 user인 빈의 name 프로퍼티를 의미한다. SpEL은 일반 프로그래밍 언어 수전에 가까운 강력한 표현식 기능을 지원한다. 다른 빈의 프로퍼티에 접근가능할 뿐만 아니라 메서드 호출도 가능하고 다양한 연산도 지원하며 클래스 정보에도 접근할 수 있다. 심지어 생성자를 호출해서 객체를 생성할 수도 있다.

SpEL(Spring Expression Language)를 이용하는 방법은 프로퍼티의 대체 위치를 설정해두고 빈 팩토리 후 처리기가 바꿔주길 기다리는 수동적인 방법과는 달리 다른 빈 오브젝트에 직접 접근할 수 있는 표현식을 이용해 원하는 프로퍼티 값을 능동적으로 가져온다.

별도의 설정이 없다면 다음과 같이 설정 파일에 작성된 값을 사용할 수 있는 것이다.

database.username= jiyoon
database.password= password
@Value("#{environment['database.username']}")
private String username;

@Value("#{environment['database.password']}")
private String password;

이는 environment라는 이름의 빈으로부터 해당 키에 해당하는 값을 읽어오는 것이다. 여기서 가져오는 Bean은 Properties타입 등의 빈이 될 수 있다.

SpEL을 사용하면 오타와 같은 실수가 있을때 에러 검증이 가능하다는 장점이 있다. 그래서 이름을 잘못 적는 실수가 있어도 예외가 발생하지 않는 ${} 기반의 프로퍼티 치환자 방식보다는 SpEL의 사용을 권장한다.

 

여기서 우리가 참고해야 할 점은 @Value 어노테이션은 스프링 컨테이너가 참조하는 정보를 의미할 뿐, 그 자체로 값을 넣어주는 것은 아니다. 그래서 Spring 컨테이너 밖에서 사용되면 @Value 어노테이션은 무시된다.

 

[타입의 변환]

설정 파일의 내용들은 모두 텍스트로 작성된다. 값을 넣을 프로퍼티 타입이 String이라면 아무런 문제가 없지만 그 외의 타입인 경우는 타입 변환과정이 필요하다.

이를 위해 스프링은 2가지 종류의 타입 변환 서비스를 제공한다.

 

1. PropertyEditory

디폴트로 사용되는 타입변환기는 java.beans의 PropertyEditor 인터페이스를 구현한 것이다. PropertyEditor는 원래 GUI 개발 환경을 위해 개발되었지만 이제는 XML에 작성된 내용이나 @Value의 String 값에서 프로퍼티 타입으로 변경하기 위해 사용된다.

PropertyEditory는 boolean, Boolean, int, Integer 등의 기본 타입의 변환과 배열의 변환 외에도 CharSet, Class, File, Currency, Locale, Pattern, Resource, Timezone, URI, URL 등의 변환을 제공한다.

 

database.url= jdbc:mariadb://localhost/security?characterEncoding=utf-8
@Value("${database.url}")
private URI url;

이렇게 다양한 타입으로의 변환이 가능한 이유는 각각의 타입 변환기를 Spring이 구현해두었기 때문이다. 예를 들어 String을 URI로 바꾸는 경우에 URIEditor, Charset으로 바꾸는 경우에 CharsetEditor가 사용되는 것이다.

만약 Spring이 지원하지 않는 타입의 오브젝트를 직접 값으로 주입하고 싶다면, PropertyEditory 인터페이스를 구현해서 직접 변환기를 구현할 수도 있다. 물론 권장하는 방법은 아니다. 이러한 방법 대신에 빈을 등록하고 DI 받는 방법이 있기 때문이다.

또한 PropertyEditory는 내부적으로 상태값을 관리하기 때문에 멀티 쓰레드 환경에서 사용이 불가능하며 String <-> Object 관계의 변환만 가능하다는 단점이 있다.

 

2. ConversionService

스프링 3부터는 PropertyEditory 대신 사용할 수 있는 ConversionService를 지원하기 시작했다. ConversionService는 자바빈에서 차용해오던 PropertyEditory와 달리 스프링이 직접 제공하는 타입 변환 API이다.

ConversionService는 변환기의 작성이 간편하며 PropertyEditory와 달리 멀티 쓰레드 환경에서 공유해 사용할 수 있다는 장점이 있다. 만약 우리가 conversionService라는 이름의 빈으로 ConversionService 타입의 빈을 등록하면 스프링 컨테이너가 이를 자동 인식해서 PropertyEditor를 대신하여 사용한다.

예를 들어 String을 int로 바꾸는 경우에 직접 만든 Converter를 사용하려면 다음과 같이 구현할 수 있다.

@Configuration
public class ConverterConfig {

    @Bean
    public ConversionServiceFactoryBean conversionService() {
        final ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
        conversionServiceFactoryBean.setConverters(converters());
        
        return conversionServiceFactoryBean;
    }
    
    @Bean
    public Set<Converter> converters() {
        final Set<Converter> set = new HashSet<>();
        set.add(new CustomConverter());
        
        return set;
    }
}
@Component
public class CustomConverter implements Converter<String, Integer> {

    public Integer convert(String username) {
        try {
            return Integer.parseInt(username);
        } catch (NumberFormatException e) {
            return 0;
        }
    }
}

물론 Spring에서 거의 모든 Conversion 기능을 제공해주기 때문에 거의 사용할 일이 없지만 특별하게 값을 변환해주어야 하는 경우라면 위와 같이 CustomConverter를 만들어서 등록하면 된다.

 

 함께보면 좋은 글 

https://frogand.tistory.com/131

 

[Spring Boot] @ConfigurationProperties 사용법

@ConfigurationProperties Spring Boot에서 *.properties나 *.yml 파일에 있는 property를 자바 클래스에 값을 바인딩하여 사용할 수 있게 해주는 어노테이션 Spring Boot에서는 운영에 필요한 정보를 프로퍼티 파..

frogand.tistory.com

 출처 

https://mangkyu.tistory.com/167

 

[Spring] 설정 값 분리의 필요성과 @Value의 사용법 및 동작 과정

이 내용은 토비의 스프링 2권의 1장 내용을 참고하며 작성하였습니다. 1. 설정 값 분리의 필요성 [ 설정 값 분리의 필요성 ] 개발을 진행하다 보면 데이터베이스 연결 정보나 외부 API 주소 등

mangkyu.tistory.com

 

728x90
반응형
blog image

Written by ner.o

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

반응형

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

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