[Grails] Domain 클래스의 일부만 유효성 검사(Validation) 하기

Published on: 2014. 5. 30. 17:36 by louis.dev

Grails에서 Domain의 Validation(유효성 검사)는 3번에 걸처 수행됩니다.


1. request의 파라미터에서 Domain object 로 변환될 때

2. Domain.validation() 메소드를 실행시킬 때

3. Domain.save() 와 같이 디비에 저장하는 메소드를 실행시킬 때


위와 같은 단계를 진행해 오면서 Domain class의 모든 필드들에 대한 유효성 검사를 하고, 유효성 검사에 문제가 생기면 Domain의 errors 필드에 유효성 검사 에러메세지가 저장되게 됩니다.


그러나 때에 따라 일부 필드들만 유효성 검사를 진행해야 하는 경우가 있는데, 이경우는 아래와 같이 하면 됩니다.



class Person {
	String ssn;
	String name;
	String address;

	static constraints = {
		ssn nullable:false
		name nullable:false
		address nullable:true
	}
}

Person person = new Person()
person.validation(['ssn', 'name']) //ssn과 name만 유효성 검사를 진행하고 address에 대한 validation은 진행하지 않게 됩니다.

//만약 클래스의 모든 필드 중 일부 필드에 대한 유효성검사만 제외하고 싶으면
def allField = Person.declaredFields.collectMany {!it.synthetic ? [it.name] : []}		//Person 도메인의 모든 필드를 리스트로 가져온다.
person.validation(allField - ['name', 'address'])	// 모든필드에서 name, address를 제외한 나머지 필드 즉 ssn만 validation을 진행한다.

//일부만 유효성 검사를 거친 도메인 오브젝트를 저장하려면 validate:false 를 사용하면 된다.

Person pserson = new Person()
person.validation(person.validation(allField - ['name', 'address']))
person.save(validate: false)

'Framework > Grails' 카테고리의 다른 글

[Grails] H2 DB 외부에서 접속하기(Server Mode)  (0) 2014.05.30

[Grails] H2 DB 외부에서 접속하기(Server Mode)

Published on: 2014. 5. 30. 16:51 by louis.dev

Grails에서 따로 추가적으로 설정하지 않으면 H2 DB가 embeded 모드로 실행이 되게 됩니다. embeded mode로 실행하게 되면 H2 DB와 함께 실행되는 웹어플리케이션의 접근속도는 빠르지만, 외부의 다른 어플리케이션 혹은 DB Client 어플리케이션에서 접근할 수가 없습니다.


동일한 DB를 여러 어플리케이션에서 공유해서 쓴다거나, DB Client 프로그램을 사용하려면 H2 DB를 embeded mode가 아닌 server mode로 실행을 해야 합니다. server mode는 H2 프로세스가 웹 어플리케이션과 분리되어, 여러어플리케이션에서 동일한 디비를 공유하여 사용할 수 있습니다.


설정방법

1. DataSource.groovy 의 dataSource 부분에 url을 선언합니다.

production {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:/db/file/path;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE;"
            //이하 설정 생략...
        }
}

URL 부분을 위와 같이 설정합니다. 위의 설정에서 Server mode라고 해서 특별히 달라지거나 할것은 없습니다. 단지 위의 내용에서 /db/file/path 을 각 서버 환경의 디비 파일을 저장할 경로를 지정해 주면 끝납니다. 위의 URL만 설정을 하면 H2 DB는 embeded mode로 실행되기 때문에 추가적으로 설정해야 합니다.


2. grails-app/conf/spring/resources.groovy에 h2Server bean을 생성합니다.

import grails.util.Environment
import org.h2.tools.Server

// Place your Spring DSL code here
beans = {
	//서버모드로 실행하기
    if(Environment.current == Environment.PRODUCTION) {
        h2Server(Server, "-tcp, -tcpAllowOthers, -tcpPort, 8043") { bean ->
            bean.factoryMethod = "createTcpServer"
            bean.initMethod = "start"
            bean.destroyMethod = "stop"
        }
    }
}

위와 같이 h2Server 라는 이름을 가진 Spring bean을 생성해 주면됩니다. 여기서 눈여겨 봐야 할 부분은 -tcpPort 뒤의 숫자입니다. 해당 숫자는 실제 외부에서 접속할때 사용하는 TCP포트 번호가 됨으로 사용자에 따라 지정해 주면 됩니다.


저는 서버모드는 실제 서비스 환경에서만 필요함으로 Environment.current == Environment.PRODUCTION 이 로직을 추가해 주었습니다.



이렇게 설정하면 H2 DB는 Server Mode로 동작하게 됩니다.


해당 디비를 외부에서 접속하려면


jdbc:h2:tcp://h2ServerDomain:${resources.groovy에서 지정한 TCP 포트번호}/{DataSource.groovy에서 설정한 DB파일 경로}


로 지정하면 됩니다.






[iBatis] queryWithRowHandler 메소드를 사용한 RowHandler사용하기

Published on: 2013. 12. 18. 15:15 by louis.dev

iBatis는 DB쿼리문의 결과를 Java Object로 매핑시켜주는 Persistent Layer의 Framework로서 DB쿼리로 검색된 결과를 다양한 자바 오브젝트( Custom Object, List, Map 등)로 생성, 매핑 시켜주어 DB 쿼리 결과를 자바단에서 쉽게 사용할수 있도록 해줍니다.


그러나 수행한 쿼리의 Row수가 대량일 경우 iBatis는 문제를 일으키게 됩니다.


예를 들어 특정 쿼리로 검색된 row의 수가 천만건이 된다고 하면, iBatis는 해당 쿼리 결과를 자바 오브젝트에 매핑 시키기 위해 사이즈가 천만인 ArrayList 인스턴스를 생성하게 될것입니다. 그렇게 대량의 자바 인스턴스를 생성하게 되면 JVM에서 사용하는 메모리사이즈를 초과하게 되어 결국 Out of Memory Exception를 뿜으며 시스템이 뻗어버리게 됩니다.


위와 같이 대량의 데이터를 셀렉트 해오는 경우에는 RowHandler를 사용하여 Out Of Memory Exception을 피해갈수 있습니다.


RowHandler를 생성하는 방법은 com.ibatis.sqlmap.client.event.RowHandler 인터페이스를 impliments 하면 됩니다.

public class TestRowHandlerCallback implements RowHandler {
	@Override
	public void handleRow(Object arg0) {
		TestClass class = (TestClass) arg0;
		//TODO select된 row 하나하나가 Java Object로 생성되어 넘어온다.
	}
}

이렇게 만들어진 RowHandler는 SqlMapClient를 사용할때 queryWithRowHandler라는 메소드를 사용하여 RowHandler를 사용하도록 지정하면 됩니다.

TestRowHandlerCallback callback = new TestRowHandlerCallback();
//getSqlMapClientTemplate().queryWithRowHandler("Test.rowHandlerTestQuery", callback);		//스프링 사용시
getSqlMapClient().queryWithRowHandler("Test.rowHandlerTestQuery", callback);

이렇게 queryWithRowHandler 메소드로 RowHandler를 지정하게 되면 iBatis는 List 인스턴스를 생성하지 않고 Row당 하나의 자바 오브젝트를 생성한 후 RowHandler의 handleRow 메소드를 실행시킵니다. 


이렇게 되면 아무리 많은 row가 검색되었다고 하더라도 List Object를 생성하지 않기 때문에 Out Of Memory Exception이 발생하지 않게 되어 안전하게 개발할 수 있습니다.






[Spring 3.2] Interceptor에서 Controller의 어노테이션 체크하기

Published on: 2013. 12. 2. 19:45 by louis.dev

인증이 필요한 서비스를 개발할 경우, Interceptor에서 로그인 유무를 확인하는 로직을 추가하여 각 컨트롤러에 인증 로직을 추가로 작성하지 않도록 할수 있습니다.


만약 이때 어떤 컨트롤러의 메소드는 로그인을 체크하고 어떤 컨트롤러의 메소드에는 로그인을 체크하지 않도록 하려면 어떻게 해야 할까요?


스프링 3.2 부터는 mvc:interceptor 부분에 exclude-mapping 이라는 엘리먼트가 추가되어 기존의 스프링 버전보다 쉽게 인터셉터의 적용을 제외시킬수 있습니다.

<mvc:interceptor> <mvc:mapping path="/**"/> <exclude-mapping path="/info/**"/> <bean class="net.krespo.interceptor.LoginCheckInterceptor" /> </mvc:interceptor>

이렇게 exclude-mapping을 사용하면 LoginCheckInterceptor는 /info/ 로 시작하는 컨트롤러에 인터셉터 적용을 제외 시킬수 있습니다.


그런데 만약 /info/my는 로그인 인터셉터를 수행시키고 /info/test는 로그인 인터셉터를 수행시키려면 어떻게 해야할까요?


물론 exclude-mapping을 여러개 등록하면 되지만 로그인이 필요하지 않은 컨트롤러가 생길때마다 exclude-mapping을 선언하는것이 귀찮게 느껴질겁니다.


이럴때는 임의의 어노테이션을 생성하여 인증이 필요하지 않은 컨트롤러의 메소드에 어노테이션을 붙이고, 어노테이션 유무를 인터셉터에서 확인하여 로그인을 할지 안할지를 하면 좀더 편하게 개발을 진행할 수 있을것 같습니다.


이제부터 본격적으로 인터셉터에서 컨트롤러의 어노테이션을 체크하는 방법을 알아보도록 하겠습니다.


1. 어노테이션 생성

package net.krespo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoLoginCheck {
}

NoLoginCheck라는 어노테이션을 생성하였습니다. 이 어노테이션을 사용하여 어노테이션을 메소드에 추가를 했으면 로그인 체크를 하고, 어노테이션이 없으면 로그인 체크를 하지 않도록 할것입니다.


2. 컨트롤러에 어노테이션 추가


@Controller
public class MyController {
	
	@RequestMapping("/my/info")
	@NoLoginCheck 
	public ModelAndView myInfo(){
		...
	}
	
}

3. Interceptor에서 어노테이션 체크하기

public class LoginInterceptor extends WebContentInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
		//@NoCheckLogin 어노테이션이 컨트롤러에 사용되었는지 체크함
		NoCheckLogin usingAuth = ((HandlerMethod) handler).getMethodAnnotation(NoCheckLogin.class);

		//NoCheckLogin 어노테이션이 없음으로 무조건 로그인 체크
		if(usingAuth == null) {
			//TODO 로그인 체크
		}
		//NoCheckLogin 어노테이션이 없음으로 로그인 체크 하지 않음
		else {
			//TODO 추가작업
		}
	}	
}

가장 중요한 부분은 preHandler 메소드의 handler Object를 HandlerMethod로 타입케스팅 하는 부분입니다. 이렇게 HandlerMethod로 바꾼 handler오브젝트의 getMethodAnnotation을 통해서 앞으로 실행될 컨트롤러의 메소드에 해당 어노테이션이 추가가 되었는지 안되어 있는지를 확인할 수 있습니다.


위 내용은 스프링 3.2에서 확인해 보았습니다. 그러나 스프링 3.0에서는 해당 handler Object를 HandlerMethod의 오브젝트가 아닌 앞으로 실행시킬 컨트롤러오브젝트(위의 예제에서는 LoginController)가 반환이 됨으로 위의 코드는 3.0에서는 사용할 수가 없습니다~(3.1에서는 체크를 해보지 못했습니다. 3.1에서 사용해 보신 분께서는 댓글로 알려주세요~^^)

[Spring 3.2] @ControllerAdvice를 이용한 익셉션 처리

Published on: 2013. 11. 29. 19:23 by louis.dev

스프링 프레임웍 3.2 이상부터 @ControllerAdvice라는 어노테이션이 추가 되었습니다. 이 어노테이션을 사용하면 간단하게 익셉션 발생시 작업을 처리할 수가 있습니다.


방법은 간단합니다. 설정파일에 아래와 같이 설정합니다. 아래의 설정 내용은 따로 설명하지 않겠습니다.

<annotation-driven />
<context:component-scan base-package="net.krespo" >

이후 적당한 위치에 클래스를 하나 생성하여 @ControllerAdvice를 붙여주면 됩니다.

package net.krespo.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;


@ControllerAdvice
public class AnnotationExceptionHandler {
	@ExceptionHandler(Exception.class)
	public void handleException(Exception e) {
		System.out.println("exception");
	}
	
	@ExceptionHandler(RuntimeException.class) 
	public ModelAndView handleRuntimeException(RuntimeException e) {
		ModelAndView mnv = new ModelAndView("exceptionHandler");
		mnv.addObject("data", e.getMessage());
		
		return mnv;
	}
}

클래스에는 @ControllerAdvice를 선언해 주고 각 메소드 마다 @ExceptionHandler라는 어노테이션으로 어떤익셉션을 처리할 것인지를 선언해 주면 됩니다.


익셉션을 처리하는 메소드는 익셉션 파라미터를 받을 수 있습니다. 그래서 개발자가 익셉션에 메세지를 담아서 던지면 해당 메세지를 각 핸들러 메소드에서 확인할수 있습니다.


또한 리턴값으로는 void부터 ModelAndView까지 다양하게 리턴할수 있어 익셉션을 처리하는데 편리하게 사용할 수 있습니다.