'Development > Java' 카테고리의 다른 글
Garbage Collection 방식 (0) | 2018.05.12 |
---|---|
Garbage Collection 과정 (0) | 2018.04.28 |
AES256 암호화 오류 해결 (4) | 2017.10.06 |
반복문 성능 비교 (0) | 2017.07.27 |
List 중복 제거 (0) | 2017.07.13 |
Garbage Collection 방식 (0) | 2018.05.12 |
---|---|
Garbage Collection 과정 (0) | 2018.04.28 |
AES256 암호화 오류 해결 (4) | 2017.10.06 |
반복문 성능 비교 (0) | 2017.07.27 |
List 중복 제거 (0) | 2017.07.13 |
public boolean isAdmin() {
return StringUtils.equals(RoleCodes.ADMIN, role.getName());
}
public boolean isManager() {
return StringUtils.equals(RoleCodes.MANAGER, role.getName());
}
public boolean isUser() {
return StringUtils.equals(RoleCodes.USER, role.getName());
}
들여쓰기 무시하기
public String render() { return ""; }
public String render() {
return "";
}
팀에서 포괄적인 규칙을 정하여 IDE에 적용하여 사용하다가 의아스러운 부분이 있었다.
상세한 부분에 대해서도 팀 내 규칙을 정하여 한 사람이 작성한듯한 가독성 좋은 코드를 작성해야 한다.
적용한 룰에 예외적으로 느껴진 항목
참고문헌 - CleanCode 애자일 소프트웨어 장인 정신
Meaningful Name (0) | 2017.12.31 |
---|
코드의 일관성을 깨뜨리지 말라.
ex)
String codeList;
// 실제 데이터를 담는 컨테이너가 List가 아닌데 변수명으로 list를 사용하는 경우
int a = l;
if (O == l)
a = 01;
else
l = Ol;
// 문자 l은 숫자 1처럼 보이고 문자 O는 숫자 0 처럼 보인다.
ex)
지금까지 구현한 모든 add 메소드가 기존 값 두개를 더하거나 이어서 새로운 값을 만든다고 가정하자.
새롭게 작성하는 메소드는 집합에 값을 하나만 추가한다.
일관성 있게 add라는 메소드명을 사용해야 하는가? 아니다.
insert나 append 등 다른 이름을 사용하자.
ex)
firstName, lastName, street, houseNumber, city, state, zipcode 라는 변수가 있다.
변수를 훑어보면 주소라는 사실이 금방 알아챈다.
하지만 어느 메소드가 state라는 변수 하나만 사용한다면?
변수 state가 주소의 일부라는 사실을 금방 알아챌까?
addr 접두어를 추가해 addrFirstName, addrLastName, addrState라고 쓰면 맥락이 분명해진다.
ex)
Gas Station Deluxe 라는 어플리케이션을 작성한다고 해서 클래스 이름 앞에 GSD를 붙이지는 말자. GSDAccount, GSDUser ..
G를 입력하고 자동완성을 누를 경우 모든 클래스가 나타나는 등 효율적이지 못하다.
개발자를 지원하는 IDE를 방해할 이유가 없다.
좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 의미가 뚜렷한 어휘인지 분별할 줄 알아야 한다.
다른 개발자가 반대할까 두려워 이름을 바꾸지 않아서는 안된다.
나름대로 수정했다가 질책을 받더라도 개선하려는 노력을 중단해서는 안된다.
그래야 자연스럽게 읽히는 코드를 짜는 데에 더 집중할 수 있다.
단기적인 효과는 물론 장기적인 이익도 보장한다.
참고문헌 - CleanCode 애자일 소프트웨어 장인 정신
Formatting (0) | 2018.03.02 |
---|
multiple 'X-Frame-Options' headers with conflicting 이슈 해결
동일 도메인에서 <iframe>
을 통해 접근하는 경우 X-Frame-Options을 DENY로 설정하면 최신(?) 브라우저에서는 접근할 수 없는 문제가 발생할 수 있다. 이 경우 동일 도메인에서는 <iframe>
접근이 가능하도록 X-Frame-Options를 SAMEORIGIN으로 설정해줘야 한다.
<iframe>
을 사용하는 부분이 있어 기존에 아래와 같이 SAMEORIGIN으로 설정해서 잘 사용하고 있었다.
http.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN));
그런데 스프링 시큐리티 버전(3.2.3 -> 4.0.4)을 올리면서 동작에 문제가 생겼다. 브라우저에서 아래와 같은 에러가 발생했다.
[Chrome]
in a frame because it set multiple 'X-Frame-Options' headers with conflicting values ('DENY, SAMEORIGIN'). Falling back to 'deny'
http 응답 헤더를 보니 DENY와 SAMEORIGIN 설정이 중첩되는 문제가 발생했다.
스프링 시큐리티 버전이 올라가면서 default 값인 DENY에 SAMEORIGIN이 추가되어 충돌난 듯하다.
내용을 찾아보니 스프링 시큐리티 4.x 부터 설정하는 방식이 변경되었다.
http.headers().frameOptions().sameOrigin();
위와 같이 설정하니 문제없이 잘 동작하는 것을 확인했다.
DatabaseConfiguration 적용 (0) | 2018.08.17 |
---|---|
Configuration Condition (0) | 2018.07.16 |
비동기 메소드 HttpServletRequest 에러 (0) | 2017.12.03 |
비동기 어노테이션 (0) | 2017.11.25 |
캐시 어노테이션 (0) | 2017.10.06 |
비동기 메소드 HttpServletRequest 이슈 해결
@Async로 선언된 비동기 메소드를 호출했다. 메소드는 해당 클래스에 @Autowired된 HttpServletRequest로 부터 정보를 가져오다가 SimpleAsyncUncaughtExceptionHandler.handleUncaughtException() 예외가 발생했다.
No thread-bound request found: Are you referring to request attributesoutside of an actual web request, or processing a request outside of theoriginally receiving thread.
원인은 비동기 처리시 다른 스레드에서 동작하기 때문이다.
// WebApplicationInitializer 설정에 리스너 추가
servletContext.addListener(new RequestContextListener());
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attr.getRequest();
DatabaseConfiguration 적용 (0) | 2018.08.17 |
---|---|
Configuration Condition (0) | 2018.07.16 |
multiple 'X-Frame-Options' headers with conflicting 에러 (0) | 2017.12.15 |
비동기 어노테이션 (0) | 2017.11.25 |
캐시 어노테이션 (0) | 2017.10.06 |
Tomcat Cookie Domain 이슈 해결
기존 서비스를 Tomcat7에서 Tomcat8로 업그레이드 하는 과정에서 문제가 발생했다.
원인을 찾아보니 서비스 내 쿠키 생성시 서브 도메인에서도 같이 사용 할 수 있도록 .xxx.com
을 도메인으로 생성하고 있었다.
그런데 Tomcat8에서 도메인이 .(dot)으로 시작하는 쿠키를 생성할 수 없도록 변경되었다. (RFC 6265 Cookie Processor 정책)
<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />
참조 - https://stackoverflow.com/questions/29608550/tomcat-cookie-domain-validation
Web & WAS (0) | 2016.12.12 |
---|---|
Tomcat 서비스 설치/삭제 (0) | 2016.10.29 |
Spring @Async
// Java Config
@Configuration
@EnableAsync
public class SpringAsyncConfig {
...
}
// XML Config
<task:executor id="myexecutor" pool-size="5"/>
<task:annotation-driven executor="myexecutor"/>
@Async
public void asyncMethodWithVoidReturnType() {
System.out.println("Execute method asynchronously. "
+ Thread.currentThread().getName());
}
@Async
public Future<String> asyncMethodWithReturnType() {
System.out.println("Execute method asynchronously - "
+ Thread.currentThread().getName());
try {
Thread.sleep(5000);
return new AsyncResult<String>("hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
@Configuration
@EnableAsync
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
System.out.println("Execute method with configured executor - " + Thread.currentThread().getName());
}
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("TEST_");
executor.initialize();
return executor;
}
}
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("TEST_");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
// Custom 가능
// return new CustomAsyncExceptionHandler();
}
}
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
System.out.println("Exception message : " + throwable.getMessage());
System.out.println("Method name : " + method.getName());
for (Object param : obj) {
System.out.println("Parameter value : " + param);
}
}
}
Spring Boot 기반의 웹 애플리케이션은 HTTP 요청이 들어왔을 때 내장된 서블릿 컨테이너가 관리하는 독립적인 1개의 Worker 쓰레드에 의해 동기 방식으로 실행된다. 하지만 요청 처리 중 @Async가 명시된 메소드를 호출하면 앞서 등록한ThreadPoolTaskExecutor 빈에 의해 관리되는 또 다른 독립적인 Worker 쓰레드로실행된다. 별도의 쓰레드로 동작하기 때에 원래의 요청 쓰레드는 그대로 다음 문장을 실행하여 HTTP 응답을 마칠 수 있다.
Async 어노테이션을 사용하는 순간 서로 다른 Thread에서 동작하기 때문에 앞에서 생성한 Transaction이 연장되지 않으므로 주의해야 한다.
DatabaseConfiguration 적용 (0) | 2018.08.17 |
---|---|
Configuration Condition (0) | 2018.07.16 |
multiple 'X-Frame-Options' headers with conflicting 에러 (0) | 2017.12.15 |
비동기 메소드 HttpServletRequest 에러 (0) | 2017.12.03 |
캐시 어노테이션 (0) | 2017.10.06 |
@Entity : JPA를 사용해 테이블과 매핑할 클래스로 지정하는 어노테이션.
@Table : 엔티티와 매핑할 테이블을 지정하는 어노테이션.
@Table(name = "t_user", indexes = { @Index(name = "idx_u_username", columnList = "username") },
uniqueConstraints = @UniqueConstraint(name="uk_user", columnNames = {"email", "username" }))
create : 기존 테이블 삭제후 다시 생성. drop + create
create-drop : 위 과정에 추가로 앱 종료시 DDL 제거. drop + create + drop
update : DB 테이블과 엔티티 매핑정보를 비교해 변경사항만 수정.
validate : DB 테이블과 엔티티 매핑정보를 비교해 차이가 있으면 경고. 앱 실행X. DDL 변경X.
none : 자동생성기능 사용안함.
JPA 기본 환경설정
@Configuration
public class DataConfig {
@Autowired
private DatabaseProperties databaseProperties;
@Bean
public DataSource dataSoure(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(databaseProperties.getDriver());
dataSource.setUrl(databaseProperties.getUrl());
dataSource.setUsername(databaseProperties.getUsername());
dataSource.setPassword(databaseProperties.getPassword());
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSoure());
factoryBean.setPackagesToScan("com.example.model");
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
// JPA 상세설정
Properties jpaProperties = new Properties();
jpaProperties.put(AvailableSettings.SHOW_SQL, true); // SQL 보기
jpaProperties.put(AvailableSettings.FORMAT_SQL, true); // SQL 포맷팅하기
jpaProperties.put(AvailableSettings.USE_SQL_COMMENTS, true); // SQL 코멘트 보기
jpaProperties.put(AvailableSettings.HBM2DDL_AUTO, databaseProperties.getHbm2ddlAuto()); // DDL 자동생성 종류
jpaProperties.put(AvailableSettings.DIALECT, databaseProperties.getDialect()); // 방언 설정
jpaProperties.put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, true); // 키 생성 전략 사용시 설정
jpaProperties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy"); // 자바의 카멜 표기법을 테이블의 언더스코어 표기법으로 매핑
factoryBean.setJpaProperties(jpaProperties);
return factoryBean;
}
}
Board board = new board();
board.setId("id1");
em.persist(board);
// @Id로 지정한 id 필드에 Set메소드로 직접 할당하는 방법
영속성 컨텍스트는 엔티티를 영속 상태로 만드려면 식별자 값이 반드시 있어야 한다.
@Id, @GeneratedValue(strategy = GenerationType.매핑전략, generator = 식별자 생성기 이름)
Sequence 테이블 생성 DDL
CREATE TABLE MY_SEQUENCE{
SEQUENCE_NAME VARCHAR(255) NOT NULL,
NEXT_VAL BIGINT,
PRIMARY KEY (SEQUENCE_NAME)
}
TABLE 전략 매핑 코드
@Entity
@TableGenerator(
name = "BOARD_SEQ_GENERATOR",
table = "MY_SEQUENCE",
pkColumnValue = "BOARD_SEQ",
allocationSize = 1)
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR")
private Long id;
}
@Column : 객체 필드를 테이블 컬럼과 매핑시킨다.
@Enumerated : 열거 타입 매핑
@Temporal : 날짜 타입 매핑
@Lob : CLOB,BLOB 타입 매핑
@Transient : 특정 필드 매핑 제외
@Access : JPA가 엔티티에 접근하는 방식 지정. @Id 대체
AccessType.PROPERTY : 접근자 Getter를 사용해 접근한다.
@Id가 필드에 있냐 프로퍼티에 있냐에 따라 access 방식이 달라진다.
AccessType이 FIELD로 정의된 경우 영속화 과정에서 필드에 데이터를 설정하거나 읽어올 때,
메소드를 통하지 않고 직접 필드에 접근해서 읽어오기 때문에 Get/Set 메소드에 별도의 로직이 존재하는 경우 동작하지 않는다.
@Entity
public class Member {
@Id
private String id;
@Transient
private String firstName;
@Transient
private String lastName;
@Access(AccessType.PROPERTY)
public String getFullName(){
return this.firstName + this.lastName;
}
}
// @Id가 필드에 있으므로 필드 접근 방식을 사용하고 getFullName만 프로퍼티를 사용한다.
// Member 엔티티에 fullName 컬럼이 생성되고 firstName + lastName의 결과가 저장된다.
참고서적 - 자바 ORM 표준 JPA 프로그래밍 (김영한)
영속성 관리 (0) | 2017.01.21 |
---|---|
JPA 시작 (0) | 2016.11.20 |
JPA 소개 (0) | 2016.05.04 |
DatabaseConfiguration 적용 (0) | 2018.08.17 |
---|---|
Configuration Condition (0) | 2018.07.16 |
multiple 'X-Frame-Options' headers with conflicting 에러 (0) | 2017.12.15 |
비동기 메소드 HttpServletRequest 에러 (0) | 2017.12.03 |
비동기 어노테이션 (0) | 2017.11.25 |
java.security.InvalidKeyException: Illegal key size
JDK 1.8u151 버전? 부터 방법 변경
Garbage Collection 과정 (0) | 2018.04.28 |
---|---|
Garbage Collection 용어 정리 (0) | 2018.04.26 |
반복문 성능 비교 (0) | 2017.07.27 |
List 중복 제거 (0) | 2017.07.13 |
Reflection 클래스 정보 (0) | 2017.07.12 |