개발/JAVA (25)
스프링 부트+마이바티스에서 트랜잭션이 안먹을때

@Transactional(rollbackFor = Exception.class)


1. rollbackFor를 명시해준다

2. 메소드는 public으로 선언해준다


다른 패키지에서 안쓴다고 public으로 안했다가 삽질 경험

그 외에 다른 DataSourceTransactionManager니 @EnableTransactionManagement니

이런거 안넣어도 잘 동작함(블로그 글 기준)


[개발/JAVA] - 스프링 부트(Spring boot)에서 mybatis(oracle) 적용하기


참고

https://stackoverflow.com/questions/7085271/how-to-set-up-transaction-with-mybatis-and-spring



2  Comments,   0  Trackbacks
댓글 쓰기
코틀린으로 스프링 사용 시 ClassNotFoundException: kotlin.reflect.full.KClasses

Caused by: java.lang.ClassNotFoundException: kotlin.reflect.full.KClasses

at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_151]

at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_151]

at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) ~[na:1.8.0_151]

at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_151]

... 26 common frames omitted


코틀린으로 스프링을 사용 시 위와 같이 오류가 난다

이 경우 kotlin-reflect을 추가해주면 간단히 해결


<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect -->

<dependency>

    <groupId>org.jetbrains.kotlin</groupId>

    <artifactId>kotlin-reflect</artifactId>

    <version>1.2.41</version>

</dependency>



0  Comments,   0  Trackbacks
댓글 쓰기
H2 DB 사용 중 file is locked 발생

H2 DB를 사용 시 다음과 같은 오류 메세지를 만날때가 있습니다


The file is locked


내용 보면 알겠지만 여러 프로세스에서 동시에 접근할때 발행하는 오류입니다

제 경우 API 서버를 띄워놓고 배치를 돌릴때 이미 DB를 사용중이기 때문에 발생한 경우였습니다.

그럴 경우 설정을 다음과 같이 변경해 주시면 두개의 프로세스에서 동시 접근이 가능합니다.


datasource:
# url: jdbc:h2:file:~/test
url: jdbc:h2:~/test;AUTO_SERVER=true
username: sa
driver-class-name: org.h2.Driver


0  Comments,   0  Trackbacks
댓글 쓰기
spring profile 사용시 주의점

최근 삽질


application.yml에 값을 설정 해두고

spring:
profiles: local
sleep:
min: 100
max: 500
---

spring:
profiles: dev

sleep:
min: 500
max: 1000


application.properties에 아래와 같이 프로파일을 설정해주었다

spring.profiles.active=dev


어플리케이션 구동했으나 계속 발생하는 오류.  심지어 다른 서버에서는 정상 동작


org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sleepAspect': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'sleep.min' invalue "${sleep.min}"


오류를 유심히 보니 프로파일이 다른것으로 적용이 되고 있었음
INFO(6347)[main] [n.d.TestApplication:656] The following profiles are active: dev1


원인은 서버 환변경수에 다음과 같이 프로파일이 적용되고 있었음

$ env | grep PROFILE

SPRING_PROFILES_ACTIVE=dev1


결론은 프로퍼티보다 서버 환경변수의 값이 먼저 적용하기 때문에 쉘 스크립트에서 환변경수를 초기화

프로그램안에만 적용되기 때문에 다른 프로그램에는 영향 없음

SPRING_PROFILES_ACTIVE=

nohup java -cp application.properties -jar donnert.jar > console.log 2>&1 &



0  Comments,   0  Trackbacks
댓글 쓰기
@Scope 어노테이션 사용하기(request+ProxyMode)

[개발/JAVA] - Threadlocal을 이용하여 사용자별 요청 처리하기


ThreadLocal과 같이 보시면 좋습니다.

스프링에서 빈 Scope 타입은 여러가지 방식이 있으며 아래를 참고해 주세요.

https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html


별도의 옵션이 없다면 빈은 싱글톤으로 관리되며 부모의 속성을 따라갑니다.

즉 controller는 따로 설정을 하지 않았기 때문에 singleton으로 동작하게 되며 그 안에서 주입받은 빈 역시 scope를 request로 줘도 singleton으로 동작하게 됩니다.

빈을 생성해 주고 위에 보이는것처럼 scope를 request로 선언해줍니다. 


그리고 위처럼 컨트롤러를 구현합니다.

위 경우 Cart가 매 요청마다 생성될것 같지만 이미 Controller(부모)가 싱글톤으로 잡혀있고 빈을 주입받은 상태이기 때문에 Cart 역시 싱글톤으로 동작합니다.  결국 Cart와 Mart는 동작 상 다른 점이 없습니다.


이럴땐 아래처럼 ProxyMode를 지정하게 되면 싱글톤 객체를 중간 프록시에서 새로운 오브젝트로 전달을 해주게 됩니다.

이전 글에서 Threadlocal을 이용했지만 스프링의 빈 만으로도 쉽게 개발이 가능합니다.


https://github.com/donnert/spring-boot/tree/master/scope-proxy

0  Comments,   0  Trackbacks
댓글 쓰기
Threadlocal을 이용하여 사용자별 요청 처리하기





개발시 소스 전체에서 사용할 수 있는 전역변수처럼 데이터를 할당하고 싶을떄가 있습니다.

Threadlocal을 이용하여 이를 구현해 봅니다.


ShoppingController.java

@RestController
public class ShoppingController {

	private int melon;

	@RequestMapping("/")
	private String test() {
		this.addMelon();
		
		return String.valueOf(melon);
	}
	
	private void addMelon() {
		melon++;
	}
}

사용자별로 요청 시 카트에 멜론을 담는 기능을 구현하려고 합니다.  이 떄 카트 수량은 여러 메소드에서 사용할 수 있기 때문에 멤버변수로 선언을 합니다.


이렇게 작성 후 실행을 해보면 카운트가 초기화가 되지 않고 계속 증가하는 것을 볼 수 있습니다.  스프링의 경우 빈 관리를 싱글톤으로 하기 때문에 ShoppingController 객체는 요청 시 재사용 됩니다.  멤버변수 역시 초기화가 되지 않습니다.




아래는 Threadlocal을 이용해 사용자별 카트를 구현한 소스입니다.


ThreadRepository.java

public class ThreadRepository {
	private static ThreadLocal<Cart> threadLocal = new ThreadLocal<Cart>() {
		@Override
		protected Cart initialValue() {
			return new Cart();
		};
	};
	
	public static Cart getCart() {
		return threadLocal.get();
	}
	
	public static void remove() {
		threadLocal.remove();
	}
}

카트를 담을 저장소를 만들어 줍니다.  Thradlocal을 이용하여 Thread별로 저장소가 생성이 됩니다.


Cart.java

public class Cart {
	private int apple = 0;
	private int orange = 0;
	private int melon = 0;
	
	public void addApple() {
		this.apple++;
	}

	public void addOrange() {
		this.orange++;
	}

	public void addMelon() {
		this.melon++;
	}

	@Override
	public String toString() {
		return "Cart [apple=" + apple + ", orange=" + orange + ", melon="
				+ melon + "]";
	}
}

카트를 생성해줍니다.


ShoppingController.java

@RestController
public class ShoppingController {

	@RequestMapping("/")
	private String test() {
		ThreadRepository.remove();
		
		this.addMelon();
		
		return ThreadRepository.getCart().toString();
	}
	
	private void addMelon() {
		ThreadRepository.getCart().addMelon();
	}
}

컨트롤러가 위처럼 수정되었습니다.  ThreadRepository에서 getCart를 했을때 해당 요청 쓰래드에 대한 Cart가 나오기 때문에 소스 어떤부분에서든 해당 카트의 데이터를 넣고 빼는 것이 가능합니다.

이때 ThreadLocal을 이용하였기 때문에 요청 시 새로운 스래드가 생성되며 그 스래드에서만 사용할 수 있게 관리됩니다.


* 일반으로 WAS는 쓰래드풀을 쓰기 때문에 요청이나 응답 시 해당 Threadlocal을 초기화 하지 않을 경우 이전에 사용한 정보가 남아 있습니다.  쓰래드 재사용시 반드시 remove를 통해 초기화 시켜주어야 합니다.



참고소스

https://github.com/donnert/spring-boot/tree/master/thread-local




0  Comments,   0  Trackbacks
댓글 쓰기
한글 byte로 자르기(깨짐 없이)

쓸때마다 새로만드는 것 같아서 저장용


UTF-8일 경우

subStringBytes("블라블라블라라", 10, 3);

EUC-KR일 경우

subStringBytes("블라블라블라라", 10, 2);


public String subStringBytes(String str, int byteLength, int sizePerLetter) {
	int retLength = 0;
	int tempSize = 0;
	int asc;
	if (str == null || "".equals(str) || "null".equals(str)) {
		str = "";
	}

	int length = str.length();

	for (int i = 1; i <= length; i++) {
		asc = (int) str.charAt(i - 1);
		if (asc > 127) {
			if (byteLength >= tempSize + sizePerLetter) {
				tempSize += sizePerLetter;
				retLength++;
			}
		} else {
			if (byteLength > tempSize) {
				tempSize++;
				retLength++;
			}
		}
	}

	return str.substring(0, retLength);
}


0  Comments,   0  Trackbacks
댓글 쓰기
이클립스 플러그인(vrapper) 설정파일(vrapperrc)

2013/05/23 - [개발/JAVA] - [Eclipse] 이클립스에서 VI(VIM) 방식으로 코딩하기


아주 오래전에 올린 vrapper 플러그인 관련 글인데

.vrapperrc 설정파일이 너무 빈약해서 계속 업데이트 해왔지만 블로그로 다시 정리


" 기본설정

set ignorecase

set smartcase

set scrolloff=5

set tabstop=4

set shiftwidth=4

set expandtab

set hlsearch

set incsearch


" 입력모드 탈출(문자열에 jj있으면 탈출해버리니 주의!! 두번주의!!)

imap jj <ESC>


" 하단의 <leader>케릭터 매핑

let mapleader="g"


" ;입력시 :입력모드로 변경

nnoremap ; :

vnoremap ; :


" H와 L을 라인 앞으로 뒤로

noremap H ^

noremap L $


" gt탭좌측으로, gy탭우측으로(한 작업창안에서)

nnoremap <leader>y <C-W>gt

nnoremap <leader>t <C-W>gT


" 작업창 이동(창이 split되어 있을 경우 hjkl을 gh gj gk gl로 입력하여 이동

nnoremap <leader>h :wincmd h<CR>

nnoremap <leader>j :wincmd j<CR>

nnoremap <leader>k :wincmd k<CR>

nnoremap <leader>l :wincmd l<CR>


.ideavimrc


0  Comments,   0  Trackbacks
댓글 쓰기
스프링 부트(spring boot)로 톰캣에서 실행하기(이클립스)

스프링 부트로 개발된 웹 어플리케이션을 톰캣에 올리는 방법입니다.

이클립스에서 단독 실행 모드와 톰캣 배포 방식 두가지 다 가능하기 때문에

개발할떄는 단독으로 띄워서 개발을 하고 배포할때만 톰캣을 띄워서 쉽게 배포를 하셔도 됩니다.


pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

 

  <groupId>net.donnert</groupId>

  <artifactId>spring.boot.web</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>jar</packaging>

 

  <name>spring.boot.web</name>

  <url>http://maven.apache.org</url>

 

  <properties>

    <project.build.sourceencoding>UTF-8</project.build.sourceencoding>

    <java.version>1.8</java.version>

  </properties>

  <parent>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>1.4.0.RELEASE</version>

  </parent>

 

  <dependencies>

    <dependency>

      <groupId>org.springframework.boot</groupId>

      <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

      <groupId>org.springframework.boot</groupId>

      <artifactId>spring-boot-starter-tomcat</artifactId>

      <scope>provided</scope>

    </dependency>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>


기존에 웹에서 사용하던 파일과 동일합니다.

추가적으로 spring-boot-starter-tomcat을 넣어줍니다.


Application.java

package net.donnert.spring.boot.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(Application.class);
	}
	
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

SpringBootServletInitializer를 상속받으셔야 톰캣 기동 시 정상적으로 스프링 모듈이 동작합니다.

그리고 configure를 오버라이드 해주셔야 서블릿 컨테이너 기동 시 스프링 설정을 적용시켜줍니다.


TestController.java

package net.donnert.spring.boot.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
	@RequestMapping("test")
	public String test() {
		return "This is spring";
	}
}

그냥 테스트용 컨트롤러이니다.


테스트

Application을 실행시켜줍니다. 스프링 부트 어플리케이션이 실행이 됩니다.

http://localhost:8080/test를 실행해보시면 This is spring이 찍히는 것을 볼 수 있습니다.


톰캣에 올리기

이미 Application에 SpringBootServletInitializer를 상속받아놨기 때문에 별다른 설정은 없습니다.

톰캣에 올리기 위해 Dynamic Web Module 프로젝트로 설정해줍니다.

(프로젝트 우클릭-Properties-Project Facets-Dynamic Web Module 체크)

설정된 톰캣에 우클릭을 해서 Add and Remove를 선택하면 스프링 어플리케이션이 보일겁니다.

(톰캣 설정은 따로 안다루겠습니다.)

추가 후 모듈의 Path를 /spring로 변경합니다.

톰캣 스타트 후 http://localhost:8080/spring/test를 때려보시면 This is spring이 보입니다.



0  Comments,   0  Trackbacks
댓글 쓰기
스프링 부트(Spring boot)로 restful API 서버 개발하기

스프링 부트를 이용한 api서버를 만들겠습니다.

서버에 요청하면 json형식으로 결과를 내려주는 서버를 만들게 됩니다.


프로젝트 생성

메이븐 프로젝트를 생성해 줍니다.

new-project-Maven Project해서 생성하시던지

그냥 프로젝트 생성 후 confiture-convert to maven project를 해줍니다.

아래와 동일한 프로젝트 구조가 생성이 됩니다.

2016/09/07 - [개발/JAVA] - 스프링 부트(Spring boot)로 개발하기


라이브러리 추가

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>net.donnert</groupId>
  <artifactId>spring.boot.web</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>spring.boot.web</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceencoding>UTF-8</project.build.sourceencoding>
  </properties>
 
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.1.RELEASE</version>
  </parent>
 
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>
</project>

스프링 부트 parent와 starter-web을 달아줍니다.

배치때 사용한 것은 그냥 starter입니다. 뒤에 web을 붙여줍니다.


Application.java

package net.donnert.spring.boot.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

그냥 스타트.


Article.java

package net.donnert.spring.boot.web;

public class Article {
	
	private long seq;
	private String title;
	private String text;
	private String writer;
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public long getSeq() {
		return seq;
	}
	public void setSeq(long seq) {
		this.seq = seq;
	}
}

클라이언트로 전달 될 클래스입니다.


ArticleController.java

package net.donnert.spring.boot.web;

import java.util.ArrayList;
import java.util.List;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ArticleController {
	
	@RequestMapping(value = "list", method = RequestMethod.GET)
	public List
test() { List
list = new ArrayList
(); for(int i=1; i<=10; i++) { Article article = new Article(); article.setSeq(i); article.setText("This is text"); article.setTitle("This is title"); article.setWriter("I am writer"); list.add(article); } return list; } @RequestMapping(value = "article/{seq}", method = RequestMethod.GET) public Article detail(@PathVariable("seq") long seq) { Article article = new Article(); article.setSeq(seq); article.setText("This is text"); article.setTitle("This is title"); article.setWriter("I am writer"); return article; } }

@RestController 어노테이션을 통해 컨트롤러를 restful로 정의해줍니다.

이렇게 될 경우 @ResponseBody가 필요 없어집니다.


목록과 상세 2개의 url이 매핑되어있습니다.


테스트

Application.java를 실행하면 자체 서버가 뜹니다.

2016-10-07 15:20:52.554  INFO 4148 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/list],methods=[GET]}" onto public java.util.List net.donnert.spring.boot.web.ArticleController.test()
2016-10-07 15:20:52.556  INFO 4148 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/article/{seq}],methods=[GET]}" onto public net.donnert.spring.boot.web.Article net.donnert.spring.boot.web.ArticleController.detail(long)
2016-10-07 15:20:52.561  INFO 4148 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2016-10-07 15:20:52.562  INFO 4148 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2016-10-07 15:20:52.611  INFO 4148 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-10-07 15:20:52.611  INFO 4148 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-10-07 15:20:52.758  INFO 4148 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-10-07 15:20:53.325  INFO 4148 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-10-07 15:20:53.686  INFO 4148 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)

스프링 부트 스타터 웹에는 톰캣이 내장되어있습니다.

내장 톰캣이 8080으로 뜨는걸 볼 수 있습니다.


브라우저를 열고 url을 입력합니다.(http://localhost:8080/list)

[{"seq":1,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":2,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":3,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":4,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":5,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":6,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":7,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":8,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":9,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":10,"title":"This is title","text":"This is text","writer":"I am writer"}]

article 10개가 json형식으로 변환되어서 출력되는 것이 보입니다.


브라우저를 열고 url을 입력합니다.(http://localhost:8080/article/3)

{"seq":3,"title":"This is title","text":"This is text","writer":"I am writer"}

3번 article 역시 json으로 보여지고 있습니다. @PathVariable를 통해 url의 파라메터를 가져왔습니다.


간단하게 api서버가 완성(?)되었습니다.

logback, mybatis연결 등은 배치 예제에서와 동일한 방법으로 하시면 됩니다.

yml을 이용한 프로파일 적용 역시 동일합니다.

2016/09/23 - [개발/JAVA] - 스프링 부트(Spring boot)에서 mybatis(oracle) 적용하기

0  Comments,   0  Trackbacks
댓글 쓰기