'FRAMEWORK'에 해당되는 글 126건

  1. 2014.04.18 mybatis mapper 방식 적용
  2. 2013.12.17 스프링 2.0 레퍼런스
  3. 2013.12.15 static 메서드 또는 자바 에서 spring 빈 가져오기
  4. 2013.11.06 JNDI, TOMCAT
  5. 2013.10.17 멀티 쓰레드 환경에서 스프링빈 주의사항
  6. 2013.08.23 스프링 multi sourcedata 사용
  7. 2013.08.20 Spring Bean Life Cycle
  8. 2013.08.05 IBATIS 처리 후 Return 값
  9. 2013.07.31 springmodules의 valang을 활용한 Validation 테스트
  10. 2013.07.31 Spring Modules Validation

mybatis mapper 방식 적용

FRAMEWORK/SPRING 2014. 4. 18. 16:24



MyBatis 적용 가이드

개요

전자정부 표준프레임워크 기반 MyBatis 적용 가이드이다.

ex-dataaccess-mybatis.zip


ex-dataaccess-mybatis.zip



Step 1. pom.xml 변경

표준프레임워크 dataaccess artifact version을 다음과 같이 2.7.0으로 변경한다.

<!-- 실행환경 라이브러리 -->
<dependency>
	<groupId>egovframework.rte</groupId>
	<artifactId>egovframework.rte.psl.dataaccess</artifactId>
	<version>2.7.0</version>
</dependency>

Step 2. 환경 설정

Step 2.1 XML 설정

Spring XML 설정 파일 상(ex: context-mapper.xml)에 다음과 같은 sqlSession bean 추가한다.

Ex) context-mapper.xml

<!-- SqlSession setup for MyBatis Database Layer -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource" />
	<property name="mapperLocations" value="classpath:/sqlmap/mappers/**/*.xml" />
</bean>

Step 2.2 mapper xml 작성

MyBatis 가이드에 따라 query xml 작성한다. (2.1의 예제 상 지정된 mapperLocations 위치)

Step 3. DAO 작성

DAO의 경우는 다음과 같이 3가지 방식이 가능하다.

방식설명비고
기존 DAO 클래스 방식@Repository 지정 및 EgovAbstractMapper extends 활용기존 iBatis와 같은 방식
Mapper interface 방식Mapper 인터페이스 작성 및 @Mapper annotation 지정@Mapper는 marker annotation(표준프레임워크 제공)
Annotation 방식query xml 없이 mapper 인터페이스 상 @Select, @Insert 등을 활용Dynamic SQL 등의 사용에 제약이 있음

3.1 기존 DAO 형태로 사용하는 경우

@Repository 지정된 class가 EgovAbstractMapper를 extends 하여 insert, update, delete, selectByPk, list 메소드를 활용한다.

@Repository("deptMapper")
public class DeptMapper extends EgovAbstractMapper {
 
    public void insertDept(String queryId, DeptVO vo) {
        insert(queryId, vo);
    }
 
    public int updateDept(String queryId, DeptVO vo) {
        return update(queryId, vo);
    }
 
    public int deleteDept(String queryId, DeptVO vo) {
        return delete(queryId, vo);
    }
 
    public DeptVO selectDept(String queryId, DeptVO vo) {
        return (DeptVO)selectByPk(queryId, vo);
    }
 
    @SuppressWarnings("unchecked")
    public List<DeptVO> selectDeptList(String queryId, DeptVO searchVO) {
        return list(queryId, searchVO);
    }
}

3.2 Mapper interface 사용 방식

Mapper 인터페이스 작성 시 다음과 같이 @Mapper annotation 사용한다.

(패키지를 포함하는 클래스명 부분이 mapper xml 상의 namespace가 선택되고 인터페이스 메소드가 query id로 호출되는 방식)

@Mapper("employerMapper")
public interface EmployerMapper  {
 
    public List<EmpVO> selectEmployerList(EmpVO vo);
 
    public EmpVO selectEmployer(BigDecimal empNo);
 
    public void insertEmployer(EmpVO vo);
 
    public int updateEmployer(EmpVO vo);
 
    public int deleteEmployer(BigDecimal empNo);
 
}

이 경우는 xml 설정 상에 다음과 같은 MapperConfigurer 설정이 필요하다.

Ex: context-mapper.xml

<bean class="egovframework.rte.psl.dataaccess.mapper.MapperConfigurer">
	<property name="basePackage" value="egovframework.rte.**.mapper" />
</bean>

basePackage에 지정된 패키지 안에서 @Mapper annotation을 스캔하는 설정이다.

⇒ @Mapper로 지정된 인터페이스를 @Service에서 injection 하여 사용함

public class EmployerMapperTest {
 
    @Resource(name = "employerMapper")
    EmployerMapper employerMapper;
 
@Test
    public void testInsert() throws Exception {
        EmpVO vo = makeVO();
 
        // insert
        employerMapper.insertEmployer(vo);
 
        // select
        EmpVO resultVO = employerMapper.selectEmployer(vo.getEmpNo());
 
        // check
        checkResult(vo, resultVO);
    }

3.3 Annotation 사용 방식

mapper xml 작성 없이 Mapper 인터페이스 상에 @Select, @Insert, @Update, @Delete 등의 annotation을 통해 query가 지정되어 사용된다.

@Mapper("departmentMapper")
public interface DepartmentMapper  {
 
	@Select("select DEPT_NO as deptNo, DEPT_NAME as deptName, LOC as loc from DEPT where DEPT_NO = #{deptNo}")
    public DeptVO selectDepartment(BigDecimal deptNo);
 
	@Insert("insert into DEPT(DEPT_NO, DEPT_NAME, LOC) values (#{deptNo}, #{deptName}, #{loc})")
    public void insertDepartment(DeptVO vo);
 
	@Update("update DEPT set DEPT_NAME = #{deptName}, LOC = #{loc} WHERE DEPT_NO = #{deptNo}")
    public int updateDepartment(DeptVO vo);
 
	@Delete("delete from DEPT WHERE DEPT_NO = #{deptNo}")
    public int deleteDepartment(BigDecimal deptNo);
 
}

⇒ 이 경우는 별도의 mapper xml을 만들 필요는 없지만, dynamic query를 사용하지 못하는 등의 제약사항이 따름



출처 -http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte2:psl:dataaccess:mybatisguide

:

스프링 2.0 레퍼런스

FRAMEWORK/SPRING 2013. 12. 17. 17:41

http://openframework.or.kr/framework_reference/spring/ver2.x/html/index.html



reference_spring_2.x.zip


:

static 메서드 또는 자바 에서 spring 빈 가져오기

FRAMEWORK/SPRING 2013. 12. 15. 02:59

스프링을 사용하다보면 부득이하게 “spring bean“을 일반 자바 또는 “static 메서드“로
선언된 곳에서 spring bean을 호출을 해야 하는 경우가 있습니다.

예를 들어서 “Servlet Filter“에서 “특정 spring bean”사용 한다 거나 특히, “유틸리티” 기능을
하는 클래스들(“주로 static으로 메서드가 선언된 클래스“)에서 사용할 경우가 대표적인
예라고 할 수 있습니다.

위와 같은 경우 어떻게 사용하는지에 대해서 “recipe“를 알려 드리겠습니다.

(1) 스프링 빈 설정

먼저 호출되어질 “스프링 빈“을 간단하게 설정하도록 하겠습니다.

SampleBean.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
 * Revision History
 * Author                    Date              Description
 * ------------------        --------------  ------------------
 * @beyondj2ee for twitter   2012. 12. 6
 */
package pe.beyondj2ee.springframework.sample
 
public class SampleBean {
    //~ Static fields/initializers ~
    //~ Instance fields ~
    //~ Constructors ~
    //~ Implementation Method (interface/abstract) ~
    //~ Self Methods ~
    /**
     * 메세지를 리턴 한다.
     *
     * @return the string
     * @throws Exception the exception
     */
    public String echo () throws Exception {
        return "My Bean!!!";
    }
    //~ Getter and Setter ~
}

위의 클래스를 스프링 빈으로 설정 합니다.
bean.xml

(2) web.xml 설정

아래와 같이 “web.xml”에 스프링 컨텐스트를 설정 합니다.
여기서 중요한 부분은 “RequestContextListener” 리스너 입니다.
해당 리스너의 역할은 “현재 요청되어진 “HttpServletRequest” 객체의 레퍼런스
정보를 저장하는 기능을 갖고 있습니다.
즉 , current request에 대한 정보를 노출시켜주는 기능 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:/bean.xml</param-value>
</context-param>
 
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>
<listener
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

(2) utility 클래스 작성

“스프링 빈”를 호출해서사용하는 “utility 클래스” 입니다.
“RequestContextHolder” 클래스를 사용해서, 현재 요청한 “HttpServletRequest”
객체를 가져 옵니다.
나머지 코드는 기본적으로 스프링 빈을 가져오는 코드들 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
</pre>
/*
 * Revision History
 * Author Date Description
 * ------------------ -------------- ------------------
 * @beyondj2ee for twitter 2012. 12. 6
 */
package pe.beyondj2ee.springframework.sample;
 
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
 
/**
 * 메세지를 리턴하는 클래스.
 */
public class StaticUtil {
 //~ Static fields/initializers ~
 //~ Instance fields ~
 //~ Constructors ~
 //~ Implementation Method (interface/abstract) ~
 //~ Self Methods ~
 /**
 * 메세지 정보를 리턴 한다.
 *
 * @return the message
 * @throws Exception the exception
 */
 public static String getMessage () throws Exception {
 
 //현재 요청중인 thread local의 HttpServletRequest 객체 가져오기
 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
 
 //HttpSession 객체 가져오기
 HttpSession session = request.getSession();
 
 //ServletContext 객체 가져오기
 ServletContext conext = session.getServletContext();
 
 //Spring Context 가져오기
 WebApplicationContext wContext = WebApplicationContextUtils.getWebApplicationContext(conext);
 
 //스프링 빈 가져오기 & casting
 SampleBean sBean = (SampleBean)wContext.getBean("sampleBean");
 
 return sBean.echo();
 }
 //~ Getter and Setter ~
 
}

(2) controller 적용하기

컨트롤러에서 위의 “utility 클래스“를 호출 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
 * Revision History
 * Author                     Date              Description
 * ------------------         --------------  ------------------
 * @beyondj2ee for twitter  2012. 12. 6
 */
package pe.beyondj2ee.springframework.sample;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
@Controller
@RequestMapping("/static")
public class BasicController extends AbstractController {
    // ~ Static fields/initializers ~
    /** this logger. */
    private static final Logger logger = LoggerFactory.getLogger(BasicController.class);
 
    // ~ Instance fields ~
    // ~ Constructors ~
    // ~ Implementation Method (interface/abstract) ~
    // ~ Self Methods ~
 
    /**
     * 기본 요청을 처리하는 컨트롤러.
     *
     * @return the model and view
     * @throws Exception the exception
     */
    @RequestMapping(method = RequestMethod.GET)
    public void handleGET() throws Exception {
        logger.info("message : " + StaticUtil.getMessage());
    }
    // ~ Getter and Setter ==============================================================================================
}

컨트롤러를 실행하면 아래와 같이 결과가 출력 됩니다.
이미지 1

Conclusion

해당 방법은 “HpptServletRequest” 객체를 참조하기 어려운 클래스에서 호출시에
좋은 “tip“이 될것 입니다. 참고로 “Web Application“에서만 해당 된다는 점을
유념 하시기 바랍니다.




출처 - http://beyondj2ee.wordpress.com/2012/12/06/static-%EB%A9%94%EC%84%9C%EB%93%9C-%EB%98%90%EB%8A%94-%EC%9E%90%EB%B0%94-%EC%97%90%EC%84%9C-spring-%EB%B9%88-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0/

'FRAMEWORK > SPRING' 카테고리의 다른 글

mybatis mapper 방식 적용  (0) 2014.04.18
스프링 2.0 레퍼런스  (0) 2013.12.17
멀티 쓰레드 환경에서 스프링빈 주의사항  (0) 2013.10.17
스프링 multi sourcedata 사용  (0) 2013.08.23
Spring Bean Life Cycle  (0) 2013.08.20
:

JNDI, TOMCAT

FRAMEWORK 2013. 11. 6. 17:30

JNDI(Java Naming Directory Interface) 데이타베이스 다루는 방법

차례
소개

JNDI 데이타베이스 환경설정은 JNDI-자원-사용법에 (JNDI-Resources-HOWTO) 광범위하게 퍼져있다. 그러나, 톰캣-사용자로 부터 피드백(feedback)중에서, 특히 개인적인 환경설정에 대해 매우 까다롭게 될 수 있다는 것을 보여줬다.

여기 환경설정에 대한 몇가지 예시가 있다. 톰캣 유저들을 위한 대중적인 데이타베이스(databases)와 디비(db)사용법에 대한 몇가지 일반적인 작은 조언들(tips)이 게시되었었다.

환경설정으로부터 얻어낸 주의사항들과(혹은 이나)톰캣-사용자에게 게시된 YMMV(Your Mileage May Vary) -'귀하의 마일리지는 변할 수 있다는 뜻'를 사용자들이 잘 인식해야 한다. 만약 당신이 그밖에 다른 검증된 환경설정을 한다면, 당신은 넓은 분위기에서 사용하는 것을 느낄수도 있고, 아니면 혹시 당신이 우리가 이 부분을 어떤 방법으로든 (환경설정과 관련하여)개선할 수 있다고 느낀다면 우리에게 알려달라.

JNDI 자원 환경설정은 톰캣 5.0.x과 톰캣 5.5.x간에 약간 변했다는 것에 주의하라. 당신은 주로 더 오래된 자원 환경설정을 변경하고, 톰캣 7.x.x에서의 과업을 그들이(환경설정) 제대로 만드는 것을 위해 아래의 예시가 필요할 것이다.

또한, 일반으로 JNDI 데이터소스 설정과, 그리고 특히 이 튜토리얼에서는 여러분이 이후 레퍼런스에 나오는 자동 애플리케이션 배치에 관한 섹션을 포함해서, Context와 Host를 읽어서 이해하고 있다고 가정한다는 것을 알아주기 바란다.

드라이버 매니저, 서비스공급자 구조와 메모리 낭비

java.sql.DriverManager는 서비스 공급자구조를 지원하는 것이다. 모든 이용가능한 JDBC 드라이버들의 특징은 메타-INF/서비스/자바의 SQL 드라이버(META-INF/services/java.sql.Driver) 파일들을 자동적으로 발견하며 JDBC 드라이버 자신들을 제공하며 나타내는 것이고, 로드(load)하고, 등록하고, 당신이 JDBC접속하기 전, 데이타베이스 드라이버를 불르기 위해 필요한 사람들의 부담을 확실하게 당신으로부터 덜어준다. 하지만, 이러한 것의 실행은 서블릿(servlet)컨테이너 환경에 대한 모든 자바 버전들이 기본적으로 순조롭지 않다. java.sql.DriverManager에서의 문제점은 오직 한번 드라이버들을 자세히 살피는 것이다.

JRE 메모리 낭비 예방 리스너는 아파치 톰캣이 시작하는 동안 드라이버를 자세히 살피기 시작하면서 해결하는 것이 함께 포함된다. 이것은 기본적으로 가능하게 된다. 그것이 의미하는 것은 오직 라이브러리들을 리스너에서 보는 것이다. 예를 들면 각각의$CATALINA_BASE/lib는 데이타베이스 드라이버들에 대해 자세히 살펴보게 될것이다. 만약 이것의 특징을 당신이 바꿀것을 고려 중이라면, 사용중인 JDBC가 첫번째 웹 프로그램에 의해 사용하게 되는지 보면서 주의 하고, 웹 프로그램이 재실행되고, 다른 웹 프로그램에 대하여 JDBC에 의존하는 것이 실패가 따르는 것도 주의하라.

따라서, 웹 프로그램은 그들의 WEB-INF/lib파일목록에서 서비스공급자 구조에 의존할 수 없고, 드라이버들을 분명하게 등록해야 할 수 있는 내부에서의 데이타베이스 드라이버들을 가지고 있다.

java.sql.DriverManager는 또한 메모리 낭비의 일부라고 알려져 있다. 웹 프로그램에 의해 등록된 어떤 드라이버들은 웹 프로그램이 (작동을)멈추면 반드시 등록을 취소하게 된다. 톰캣은 자동적으로 발견하고 웹 프로그램 클래스 로더에 의해 불려진 어떠한 JDBC 드라이버들의 등록 취소는 웹 프로그램이 멈추었을 때, 시도할 것이다. 그러나, 프로그램들이 프로그램 자신을 위해ServletContextListener를 통해서 하는 것을 기대하게된다.

데이타베이스 연결 풀(DBCP) 환경설정

아파치 톰캣에서의 기본 데이타베이스 연결 풀 구현은 Apache Commons 프로젝트에서 가져온 라이브러리들에 의존한다. 다음의 라이브러리들이 사용된다:

  • Commons DBCP
  • Commons Pool

이 라이브러리들은 $CATALINA_HOME/lib/tomcat-dbcp.jar의 단 하나의 jar 안에 위치해있다. 그러나 오직 클래스들은 연결 풀링이 포함되는 것을 필요로 하고, 그리고 그런 패키지들은 프로그램들과 간섭하는 것을 피하기 위해 개명하게 되었다.

DBCP 1.4는 JDBC 4.0을 지원한다.

설치

환경설정 인수들의 전체 목록은 DBCP 문서를 보라.

Preventing database connection pool leaks

데이타베이스 연결 풀은 데이타베이스에 여러개의 공통된 접속을 만들고 관리한다. 이미 존재하고 있는 데이타베이스의 접속상태를 다시쓰고 재활용 하는 것이 (데이타베이스를)새롭게 여는것보다 더욱 효율적이다.

여기 공통으로 접속하는 것과 함께 하나의 문제점이 있다. 웹 프로그램은 명확하게 설정한 결과(ResultSet)와 문장(Statement) 그리고 접속(Connection)을 기록하고 종료처리해야 한다. 웹 프로그램의 실수를 차단 하기 위하여 이런 자원들이 절대 웹 프로그램에 재사용을 위해 다시 이용가능하게 끝낼 수 없다(데이타베이스 접속 공통 "낭비"). 만약 더 이상 이용가능한 접속이 없다면, 이것이 결국에는 당신의 웹 프로그램 데이타베이스 접속이 실패로 끝날 수 있다.

이 문제에 대해 하나의 해결책이 있다. 아파치 Commons DBCP는 이런 폐기된 데이타베이스 접속을 회복하고 추적하기 위해 환경설정을 할 수 있게 되었다. 단순히 데이타베이스연결을 회복할 뿐만 아니라, 거기에 개방된 자원들과 결코 차단되지 않은 데이타베이스 연결 코드에 대한 임시기억의 흔적을 찾아낸다.

폐기된 데이타베이스 연결들은 삭제되고, 당신의 DBCP 데이타소스를 위한 Resource 환경설정을 더하며 데이터 처리를 되풀이하며, 뒤따르는 자원에게 돌려주는 것은 DBCP 데이타자원을 설정하기 위해서이다.

removeAbandoned="true"

낮은 실행의 DBCP를 이용가능한 데이타베이스의 연결이 회복하고, 어떤 폐기된 데이타베이스를 연결에서 그것을 찾고, 같은 데이타를 반복하여 처리한다. 기본값은 false이다.

removeAbandonedTimeout을 설정하고 되돌려서 사용하기 위한 일정시간의 데이터베이스 연결은 그것이 고려되어 폐기되기 이전에 작동이 멈추었다.

removeAbandonedTimeout="60"

제거하여 폐기된 접속을 위한 기본 남은시간은 300초이다.

그런 logAbandoned 속성은 순서에 맞고, true 설정되어 질 수 있다. 만약 당신이 DBCP에 파기된 데이타베이스 접속 자원 코드의 일시적인 흔적을 기록하고 싶다면 말이다.

logAbandoned="true"

기본값은 false이다.

MySQL DBCP 예시

0. 소개

MySQL의 버전(판)과 JDBC드라이버는 작업하기 위해 보고되었다.

  • MySQL 3.23.47, MySQL 3.23.47 이노디비를 사용하고 있다(using InnoDB,). MySQL 3.23.58, MySQL 4.0.1알파
  • Connector/J 3.0.11-안정적인 (공식적인 JDBC 드라이버)
  • mm.mysql 2.0.14 (JDBC 드라이버의 오래된 3번째 그룹)

여러분은 이것을 진행하기 전에, $CATALINA_HOME/lib안에 JDBC Driver's jar를 복사하는 걸 잊지 말라.

1. MySQL 환경설정

이것을 따라하면서 변경한다면 문제가 발생할 수 있다는 것을 명심하라.

새로운 시험 사용자를 새로운 데이타베이스와 단 하나의 시험 테이블과 함께 생성하라. 당신의 MySQL 사용자는 반드시 지정된 하나의 암호를 가지고 있어야 한다. 당신이 암호가 없이 접속을 시도하려 한다면 드라이버에 접속실패 할 것이다.

mysql> GRANT ALL PRIVILEGES ON *.* TO javauser@localhost
    ->   IDENTIFIED BY 'javadude' WITH GRANT OPTION;
mysql> create database javatest;
mysql> use javatest;
mysql> create table testdata (
    ->   id int not null auto_increment primary key,
    ->   foo varchar(25),
    ->   bar int);
주의: 테스팅을 마치면 위 사용자는 제거되어야 한다!

다음으로 testdata 테이블에 테스트 데이터를 넣는다.

mysql> insert into testdata values(null, 'hello', 12345);
Query OK, 1 row affected (0.00 sec)

mysql> select * from testdata;
+----+-------+-------+
| ID | FOO   | BAR   |
+----+-------+-------+
|  1 | hello | 12345 |
+----+-------+-------+
1 row in set (0.00 sec)

mysql>

2. Context configuration

2.Context 환경설정

톰캣에서 JNDI 데이타소스는 여러분의 Context에서 당신의 자원을 위해 추가함으로써 나타내며 환경설정한다.

예를 들면:

<Context>

    <!-- maxActive: 데이타베이스 연결 풀의 최대값
  여러분은 확실하게 당신의 mysqld의 엄청난 접속을 충분히 많은 것을, 
  당신의 디비 접속에서의 모든것을 다루기 위해 환경설정하라.
  제한없기 위해 -1값에서 정하라.(0보다 작고,-값에선 무한대기)
         -->
    <!-- maxIdle: 특별한 일 없이 유지하는 데이타베이스 접속풀의 최대 갯수 
  제한없기위해 -1에서 지정하라. 이것을 또한 DBCP 문서에서 보고, 
  minEvictableIdleTimeMillis(사용되지 않는 커넥션을 추출할때 지정한 시간 
  이상 비활성화 상태인 것만 추출하는 최소한의 시간)의 환경설정 매개변수와 
  minEvictableIdleTimeMillis도 그렇게 하라. 
         -->
    <!-- maxWait: ms에서 이용가능케 되기 위한 어떤 데이타베이스 접속을 
    위해 기다리는 최대의 시간, 예를들어 10초 이다. 어떤 예외는 이것이 남는 시간이 
    초과되면 동작된다.
    지정된 시간없이 기다리게 하기 위해서 -1값으로 지정하라.
         -->



 
<!-- 사용자이름과 비밀번호: 데이터베이스 접속을 위한 MySQL 사용자이름과 비밀번호 -->

    <!-- driverClassName: 오래된 mm.mysql JDBC driver를 위한 클래스 이름은 
    org.gjt.mm.mysql.Driver -이다. 우린 Connector/J를 통해 사용하는 것을 추천한다.
    공식적인 MySQL Connector/J 드라이버에 대한 클래스 이름은 com.mysql.jdbc.Driver이다.
         -->


    <!-- url: MySQL 데이타베이스 접속을 위한 JDBC 접속경로. -->


  <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
               maxActive="100" maxIdle="30" maxWait="10000"
               username="javauser" password="javadude" driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/javatest"/>

</Context>

3. web.xml의 환경설정

테스트 프로그램을 위해 WEB-INF/web.xml을 지금 만들어보자.

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">
  <description>MySQL Test App</description>
  <resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/TestDB</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
  </resource-ref>
</web-app>

4. 테스트 코드

이제 나중에 사용하기 위해 간단한test.jsp를 지금 만들어보자.

<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<sql:query var="rs" dataSource="jdbc/TestDB">
select id, foo, bar from testdata
</sql:query>

<html>
  <head>
    <title>DB Test</title>
  </head>
  <body>

  <h2>Results</h2>

<c:forEach var="row" items="${rs.rows}">
    Foo ${row.foo}<br/>
    Bar ${row.bar}<br/>
</c:forEach>

  </body>
</html>

이 JSP페이지는 JSTL's SQL과 핵심 태그라이브러리에 사용된다. You can get it from 여러분은 아파치 톰캣 태그라이브러리 - 표준 태그 라이브러리 프로젝트 —로부터 얻을 수 있고, 당신은 확실히 1.1.x release를 얻을 수 있다. 당신이 JSTL을 얻으면, jstl.jar과 standard.jar를 당신의 web app's WEB-INF/lib의 디렉토리에 복사하라.

마지막으로 여러분의 웹 앱안에 $CATALINA_BASE/webapps 워파일(warfile)이라 불리는 DBTest.war이나 DBTest라고 불리는 하위 디렉토리안에 파일들을 배치하라.

배치하고 하면, http://localhost:8080/DBTest/test.jsp에서 브라우저를 당신의 결과물을 보기위해 지정하라.

오라클 8i, 9i & 10g

0. 소개

오라클은 MySQL 환경설정의 통상적인 사용법을 제외하고 나서, 최소한의 변화가 요구된다. :-)

드라이버는 오라클의 오래된 버전을 위해서 *.jar 파일보다 오히려 *.zip 파일이 더 배포되었을 수도 있다. 톰캣은 오직$CATALINA_HOME/lib안에서 설치된 *.jar파일을 사용하는 프로그램 일 것이다. 그러므로, classes111.zip이나classes12.zip은 .jar의 확장명으로의 개명이 필요하게 될 것이다. jarfiles이 zipfiles이기 때문에, 이 jar파일들의 압축을 풀지않아도 되고, 간단한 개명으로 언급할 것이다.

오라클 9i의 사용을 위하여 사용자는 앞으로 oracle.jdbc.driver.OracleDriver보다 oracle.jdbc.OracleDriver를 오라클이 제시했던 것처럼 대로 사용해야 한다. oracle.jdbc.driver.OracleDriver는 덜 친숙했었고, 이것의 드라이버 클래스를 지원한 다음 주요 릴리즈에서 계속 사용되지 않을 것이다.

1. 컨텍스트 환경설정

mysql 환경설정안에서의 비슷한 방식으로, 사용자는 사용자의 Context에서 사용자의 데이타소스를 정의할 필요가 있다. 여기서 우리는 하나의 데이타소스를 myoracle(마이 오라클)이라고 정의하고 유저 스캇으로 연결하기 위해 내장된 드라이버(thin driver)에서 사용중에 있다. 비밀번호는 mysid라고 불려지는 sid에서 타이거(tiger)라고 되어 있다.(주의사항: 내장된 드라이버(thin driver)와 sid(Segment IDentifier)는 tnsname이 동일하지 않다.) 스키마는 기본적인 스키마로 사용자 스캇을 위해 사용될 것이다.

1. 컨텍스트 환경설정

oci안에의 URL주소에서 변하는 저사양고객(changing thin)에 분명하게 포함되어야 한다.

<Resource name="jdbc/myoracle" auth="Container"
              type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver"
              url="jdbc:oracle:thin:@127.0.0.1:1521:mysid"
              username="scott" password="tiger" maxActive="20" maxIdle="10"
              maxWait="-1"/>

2. web.xml 환경설정

사용자는 web.xml 파일을 프로그래밍 할때, DTD에 의한 요소의 정리법을 정의한 것을 반드시 우선순위로 삼아야 한다.

<resource-ref>
 <description>Oracle Datasource example</description>
 <res-ref-name>jdbc/myoracle</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
</resource-ref>

3. 코드 환경설정

사용자는 위와 같이(사용자가 만든 필수적인 DB인스턴스나 테이블 등.) 대체가능한 데이타소스코드와 이런 비슷한 종류와 함께 동일하게 예시된 어플리케이션을 사용할 수 있다.

Context initContext = new InitialContext();
Context envContext  = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle");
Connection conn = ds.getConnection();
//etc.
PostgreSQL

0. 소개

PostgreSQL은 오라클에서 유사한 방법으로 설정되있다.

1. 필요한 파일들

$CATALINA_HOME/lib의 장소에 Postgres JDBC jar를 복사하라. 오라클과 함께, 복사한 자르(jars)파일은 그것들로 하여금 DBCP's Classloader를 찾기 위해서 해당폴더안에 있어야 한다. 이 설정은 사용자의 원활한 진행을 위해 필요하다.

2. 자원(Resource) 환경설정

여기서도 역시 사용자에게 2가지 선택권이 있다:모든 톰캣 어플리케이션 쪽에 데이타소스를 정의하여 공유시키거나 하나의 어플리케이션을 위해 특별한 데이타소스를 정의하는 것이다.

2a. 공유된자원 환경설정

사용자가 데이타소스(다양하게 공유되는 톰캣 어플리케이션)를 정의하길 바란다면 이런 옵션을 사용하거나, 사용자가 파일안에서 사용자의 데이타소스를 정의하는 것을 선호할 것이다.

저자가 이 경우에 성공한 적이 없었다., 비록 다른 사람들이 제시했어도 말이다. 여기 분명하게 제시한다.

<Resource name="jdbc/postgres" auth="Container"
          type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
          url="jdbc:postgresql://127.0.0.1:5432/mydb"
          username="myuser" password="mypasswd" maxActive="20" maxIdle="10" maxWait="-1"/>

2b. 어플리케이션 특수자원 환경설정

만약 사용자의 어플리케이션에서 그 자신이 특별하게 데이터소스를 정의하길 바란다면, 다른 톰캣 어플리케이션에 보이지 않는 옵션을 사용하라. 이 방법은 사용자의 톰캣 설치에 있어서 작은 간섭이 필요하다.

사용자의 컨텍스트를 위해 리소스 정의를 만들어라. 제시한 컨텍스트 요소는 아래와 같이 뭔가를 봐야한다.

<Context>

<Resource name="jdbc/postgres" auth="Container"
          type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
          url="jdbc:postgresql://127.0.0.1:5432/mydb"
          username="myuser" password="mypasswd" maxActive="20" maxIdle="10"
maxWait="-1"/>
</Context>

3. web.xml 환경설정.

<resource-ref>
 <description>postgreSQL Datasource example</description>
 <res-ref-name>jdbc/postgres</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
</resource-ref>

4. 데이타소스에 접근하는 것.

데이타소스 프로그램에 맞게 접근 할 때, 사용자의 JNDI 룩업에 java:/comp/env을 넣는 것을 기억하면서, 코드의 작은부분도 따라하라. 또한 주의할 것이 있는데, "jdbc/postgres"는 사용자의 선호에 따라서 함께 대체될 수 있고, 또한 제공된 것을 사용자가 정의된 파일소스 내에서 바꾸는 것을 말한다.

InitialContext cxt = new InitialContext();
if ( cxt == null ) {
   throw new Exception("Uh oh -- no context!");
}

DataSource ds = (DataSource) cxt.lookup( "java:/comp/env/jdbc/postgres" );

if ( ds == null ) {
   throw new Exception("Data source not found!");
}
DBCP 이외의 해결책들

이런 해결책들은 하나의 단순한 연결로 데이터베이스에 사용하거나(시범 이외의 다른 어떤 것도 추천하지 않는다!) 몇몇 다른 풀링(Pooling)기술이다.

오라클 8i과 OCI 고객
소개

윌스트(Whilst)는 모호하게 OCI 클라이언트가 사용하는 JNDI 데이터소스의 탄생을 언급하지 않고, 이런 점들을 오라클과 DBCP 해결책 위에 조합하게 되었다.

OCI 드라이버의 사용을 위해서, 사용자는 설치된 오라클 클라이언트를 가지고 있어야 한다. 그리고 사용자는 cd에서 오라클8i(8.1.7) 클라이언트를 설치했어야 하고, 적용가능한 JDBC/OCI driver(Oracle8i 8.1.7.1 JDBC/OCI Driver)를otn.oracle.com 에서 다운 받아야 한다.

그 다음에, classes12.zip파일을 classes12.jar으로 톰캣에서 정정하고, $CATALINA_HOME/lib에 복사하여 넣어야 한다. 사용자는 톰캣과 JDK의 버전에 이런 파일로부터 종속하고 있는 javax.sql.*클래스들을 또한 제거해야 할수도 있다.

모든 것을 다같이 대입할때

당신이 ocijdbc8.dll나 .so를 당신의 $PATH나 LD_LIBRARY_PATH에 (가능하다면 $ORAHOME\bin안에)확실히 가지고 있으면, 원래 라이브러리에 사용하고 있는 System.loadLibrary("ocijdbc8");에 단순한 테스트 프로그램에 의해 적재되어질 수 있다.

사용자는 간단한 테스트용 서블릿을 만들거나 jsp에 만들어야 한다. You should next create a simple test servlet or jsp that has these 크리스탈 라인:

DriverManager.registerDriver(new
oracle.jdbc.driver.OracleDriver());
conn =
DriverManager.getConnection("jdbc:oracle:oci8:@database","username","password");

데이타베이스의 형식(host:port:SID)이고, 만약에 지금 사용자가 사용자의 시험용 servlet/jsp의 URL에 접근하는 것을 시도한다면, 사용자가 java.lang.UnsatisfiedLinkError:get_env_handle의 근원과 함께 ServletException을 획득한다.

첫째로, UnsatisfiedLinkError는 사용자가 다음의 상황에 처해있다는 것을 알려준다.

  • 사용자의 JDBC 파일과 사용자의 오라클 클라이언트 버전(client version)이 일치하지 않는다는 것이다. 그것은 라이브러리 파일을 찾을 수 없게되었다는 메세지에서 명시한다. 예를 들어, 사용자는 오라클 버전 8.1.5와 함께 오라클 버전 8.1.6버전부터 클래스12 파일(classes12.zip file)을 사용하고 있을지도 모른다. 이런 클래스xxx파일(classeXXXs.zip file)과 오라클 클라이언트 소프트웨어 버전은 반드시 일치해야 한다.
  • $PATHLD_LIBRARY_PATH의 문제.
  • 이것으로서 당신이 otn과 $ORAHOME\jdbc\lib에서 작동할 그 디렉토리로부터 사용하고 있는 클래스12 압축파일에 대해 설명되었다.

다음으로 사용자는 ORA-06401 NETCMN: invalid driver designator에러에 대하여 경험할 수도 있다.

오라클 문서는 "원인: 로그인(연결)문자는 유효하지 않은 드라이버 설계자를 포함한다. 지침행동: 문자와 다시제출하고 연결하라." 이렇게 제시할 것이다. 데이타베이스 연결 문자를(host:port:SID의 양식에서) 다음과 같이 바꾸어라.(description=(address=(host=myhost)(protocol=tcp)(port=1521))(connect_data=(sid=orcl)))

Ed.흠, 나는 만약에 사용자가 사용자의 TNSNames를 정리하는 것이 정말로 필요하지 않는다고 봐요. 오라클 데이터베이스 관리자가 아니니까요:-)

공통적인 문제들

여기에 몇가지 공통적인 문제들이 웹 어플리케이션과 함께 데이터베이스를 사용할 때와 그것들을 어떻게 해결하는가에 있다.

간간이 발생하는 데이터베이스 연결 실패

톰캣은 자바버츄얼머신(JVM)안에서 운영한다. 이 JVM은 주기적으로 가비지 콜렉션(GC)에서 더이상 사용되어 지지 않는 자바 객체들을 지우기 위하여 운영한다. JVM을 톰캣 프리즈(freezes)안에서 코드의 실행을 위해 운영할 때, 만약 데이터베이스 연결의 설정을 위해 최대 환경설정 시간은 GC의 시간의 총량보다 적다면 사용자는 데이터베이스 연결 획득 실패할 수 있다.

장기간에 걸친 불필요한 정보정리(garbage collection)에서 자료를 모으기 위한것은 톰캣을 시작할 때, -verbose:gc 독립변수를 당신의 CATALINA_OPTS환경에 추가로 가져가고 있는 것이다. 어지러운 gc(garbage collection)는 당신의$CATALINA_BASE/logs/catalina.out 로그 파일이 장기간에 걸친 gc를 가져가는 것에 포함하면서 모든 불필요한 정보정리를 위해 자료를 포함할 것이다.

사용자의 JVM의 해당 시간에 99% 가깝게 조정된 GC는 1초보다 적게 걸린다. 나머지는 오직 몇초만이 걸릴 것이다. 드물게도, 언제든지 GC가 10초 보다 더욱 GC를 가져가야 한다.

디비 접속 시간초과는 10~15초로 되어 있는것을 확실히 해라. 이런 DBCP(DataBase Connection Pool)를 위하여 사용자는 maxWait의 사용중인 인자값을 정하라.

무작위의 접숙과 닫혀진 예외들

이런 것들은 어떤 요청있어서 디비접속의 이런 접속 풀(Pool)과 2것을 다시 닫을 때로부터 발생할 수 있다. 이런 접속 풀을 사용할 때, 접속했던 접속을 닫고 다른 요청으로 재사용을 하기 위해, 해당 풀로 다시 돌아오라. 그렇게 한다면 접속이 닫히지 않는다. 그리고 톰캣은 다중 쓰레드로 동시발생 요청을 다루기 위해 사용한다. 여기에서 톰캣 내부의 에러의 원인에 대한 이벤트 발생의 예문이 제시되있다.

  하나의 요청, 1번째 쓰레드 안의 1개의 실행중인 디비 접속.

  하나의 요청이 디비 접속을 닫는다.

  JVM(자바 버츄얼 머신)이 실행중인 1번째 쓰레드를 2번째 쓰레드로 전환한다.

  2번째 쓰레드 안에서 실행중인 2번째 요청이 디비 접속을 한다.
  (이 동일한 디비 접속은 1번째 요청에 의해 종료했다.).

  JVM은 운영중인 쓰레드(2번째 쓰레드)를 1번째 쓰레드에게 되돌려준다.

  1번째 쓰레드는 마지막 블록안에서 2번 디비 접속을 종료한다.

  JVM 2번째 쓰레드에게 운영중인 쓰레드(1번째 쓰레드)를 전환한다.

  2번째 요청의 2번째 쓰레드는 디비 접속을 사용하려 시도하지만, 1번째 요청으로 닫혀있어서 실패한다.
  

커넥션 풀에서 데이터베이스를 사용하기 위한 적절하게 쓰여진 코드의 예제가 여기 있다.

  Connection conn = null;
  Statement stmt = null;  // Or PreparedStatement if needed
  ResultSet rs = null;
  try {
    conn = ... get connection from connection pool ...
    stmt = conn.createStatement("select ...");
    rs = stmt.executeQuery();
    ... iterate through the result set ...
    rs.close();
    rs = null;
    stmt.close();
    stmt = null;
    conn.close(); // Return to connection pool
    conn = null;  // Make sure we don't close it twice
  } catch (SQLException e) {
    ... deal with errors ...
  } finally {
    // Always make sure result sets and statements are closed,
    // and the connection is returned to the pool
    if (rs != null) {
      try { rs.close(); } catch (SQLException e) { ; }
      rs = null;
    }
    if (stmt != null) {
      try { stmt.close(); } catch (SQLException e) { ; }
      stmt = null;
    }
    if (conn != null) {
      try { conn.close(); } catch (SQLException e) { ; }
      conn = null;
    }
  }
컨텍스트와 글로벌네이밍리소스(GlobalNamingResources)

비록 위의 지시한 장소가 JNDI 선언장소의 컨텍스트 요소 내부일지라도, 그것은 가능하고 때때로 바람직하게 서버 환경설정파일의 GlobalNamingResources부분에 이런 선언들을 위치시키는 것에 유의하라. 해당 자원은 글로벌네이밍리소스 부분에 위치며 있으며 서버의 컨텍스트 중에서 공유될 것이다.

JNDI 자원의 정의와 범위의 상호작용

작업 공간을 얻기 위하여, 이 공간은 반드시 정의된 <GlobalNamingResources>나 or <Context> 부분에서 데이타소스를 참조하여야 하며 사용하고 있는 재정의된 <ResourceLink>는 안된다.



출처 - http://kenu.github.io/tomcat70/docs/jndi-datasource-examples-howto.html

:

멀티 쓰레드 환경에서 스프링빈 주의사항

FRAMEWORK/SPRING 2013. 10. 17. 13:24

얼마전 “CBT Live” 테스트(실제 서비스 장비에서 성능/기능 통합 테스트)를 하면서
발생한 문제를 공유하고자 합니다.

(1) 증상

외부 시스템에 “A“라는 사용자 정보를 요청 했는데, 확인을 해보니 “B” 사용자
정보가 등록이 되었습니다.

Local” 개발 환경에서는 전혀 그런 증상이 없었고, 실제로 서비스를 하려는
CBT” 개발 환경에서 “성능/기능 테스트“를 하면서 해당 이슈가 발생을 하였습니다.

그래서 “log“를 확인했습니다.
그런데 이상한 점이 “요청을 위임하는 모듈“에서는 정확히 “A” 사용자 정보
가 있었고, “실제 서버와 통신하는 모듈“에서 “B” 사용자 정보를 호출 했습니다.
확인 결과 “Spring Bean“에 대한 개발자의 이해 부족으로 발생된 에러 였습니다.
해결 방법을 말씀 드리기전에 “Spring Application Context“에 대해서
간략하게 설명을 드리겠습니다.

(2) Spring Big Picture

아래의 그림은 “멀티 쓰레드 환경에서 스프링 빈” 관계를 직접 그려봤습니다.

이미지 12

SpringFramework” 기반으로 만든 “어플리케이션”은 기동시 “ApplicationContext“라는
Static Sharing Pool“를 생성 합니다.
좀더 쉽게 설명을 하면 하나의 “싱글톤” 패턴 방식의 구현된 오브젝트가 생성 됩니다.
(정확히는 “싱글톤 패턴”은 아닙니다. 이해를 돕기 위해서)

이렇게 생성된 “ApplicationContext” 영역에 “POJO(Plain Old Java Object) 클래스
들의 오브젝트들이 등록이 됩니다.

“POJO” 클래스는 그냥 “new 하면 스스로 생성”이 가능한 클래스의 형태를 말합니다.

이렇게 등록된 오브젝트를 “Spring Bean“이라고 합니다.
비록 “POJO” 클래스에 “static“으로 선언을 하지 않더라도, “ApplicationContext
가 이미 “Sharing“이 되어 있기때문에 “당연히 등록된 Bean”로
멀티 Thread 환경“에서 서로 공유를 하게 됩니다.
(물론 prototype일 경우는 매번 생성을 합니다.)

위에 그림을 보면 “JVM”에서 하나의 공유 “ApplicationContext”가 생성이 되며
“Spring Bean #1″, “Spring Bean #2″, “Spring Bean #3″ 등
3개의 “Bean”이 등록이 되어 있고, “1번부터 10번 Thread 모두 
동일한 Bean 오브젝트“를 사용을 합니다.

여기까지는 “SpringFramework“를 개발하신 분들은 알고 계실 겁니다.

문제는 개발자들이 Spring Bean의 멤버변수 또한 멀티쓰레드 환경에서
공유가 된다는 것을 간과한다는 것입니다.

이러한 실수는 “Thread Safe” 한 프레임웍(Servlet, Netty Handler, Camel Router, Spring Controller)들에 대한 오해 때문입니다.

문서를 보면 당연히 “Thread Safe“하다고 명시된 부분만을 확인하지, 실제 어디까지
“Thread Safe“하지 않는지 확인을 잘 안한다는 것입니다.

대부분 public 메서드”안에서 선언된 로컬 변수가 “Thread Safe”한거지 “멤버변수”
까지 “Thread Safe” 하다라는 것입니다. 즉 정확한 확인이 필요 합니다.

(3) 해결 방법 및 코딩 가이드

아래는 개발자가 문제를 일으켰단 소스 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class MemberController{
 
    @Autowired
    private UserRepository userRepository;
 
    private Member member = new Member();
 
    public void createUser(String id, String name) throws Exception {
 
        member.setID(id);
        member.setName(name);
        userRepository.insertUser(member);
 
    }
}

7 Line : 멤버변수 “Member” 오브젝트를 생성 합니다.
11 Line : 파라미터 id를 member 오브젝트의 id 속성에 값 설정을 합니다
12 Line : 파라미터 name를 member 오브젝트의 name 속성에 값 설정을 합니다

소스를 보면 위에서 언급한것 처럼 “멤버변수“를 “Bean” 최초 생성시 초기화를 합니다.
그 다음 부터는 “createUser” 메서드에서 계속 재사용을 합니다.
당연히 “createUser” 메서드는 “Thread Safe” 합니다. 하지만 문제는 “멤버변수” 입니다.

해당 코드를 아래와 같이 “리팩토링” 해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class MemberController{
 
    @Autowired
    private UserRepository userRepository;
 
    public void createUser(String id, String name) throws Exception {
        Member member = new Member();
        member.setID(id);
        member.setName(name);
        userRepository.insertUser(member);
 
    }
}

8 Line :  멤버변수 부분을 삭제하고, 대신 “createUser” 메서드에서 생성을 합니다.
코드는 상당히 간단 합니다. 간혹 물어 보시는 분들이 “저렇게 매번 생성하면” 메모리에
부담이 있는것 아니냐 하시는 분들이 있습니다.

createUser” 메서드가 종료되면 자연스럽게 “member” 오브젝트는 “GC
대상이 됩니다.

(4) Conclusion

멀티 쓰레드 환경에서 스프링빈 주의사항“에 대해서 말씀 드렸습니다.
사실 별거 아닐수 있지만 상당히 주의를 요하며, 개발시에 팀원들에게 숙지를
해야 하며, 팀장 또는 선임 개발자들은 “코드 Inspection“시 반드시
이 부분에 대한 점검이 필요 합니다.

왠만하면 “스프링”에서 “멤버변수”는 “Injection“에 사용하는 “bean“일 경우만
사용하도록 권고 드립니다.

제가 왜 이렇게 강조를 하는 이유는

디버깅이 하기가 너무 어렵다는 이유 입니다. 대부분 개발시에는
개발자 환경은 멀티 쓰레드 환경이 아닙니다. 그렇기 때문에

발견이 거의 되지를 않습니다.  대부분 개발 후반에 성능 테스트
할때 발견이 됩니다.

특정 서버가 CPU를 많이 차지 하거나, 메모리 사용률을 많이 점유하면
thread dump를 떠서 확인을 하지만 이렇게 thread간 race condition
깨진 경우는 dump떠서 확인이 힘들고, 특히나 여러 클래스에 그렇게
사용을 하면 더 찾기가 어렵습니다.

최악의 경우는 프로젝트 막바지는 “멘붕” 상태이기 때문에 “선무당”이 사람 잡는다고
전체 서버에 JDK를 다른 버전을 설치 하거나, WAS를 다시 설치 하거나, 심지어 
OS 커널 또는 버전을 바꿔야 한다든지 더 큰 상황으로 치닫을수 있습니다.

또한 수십, 수백명이 이런 코드를 양산될 경우는 더더욱 트러블 슈팅이 어렵습니다.
이점 유념하시고 개발 하셨으면 합니다.

 

 

출처 - http://beyondj2ee.wordpress.com/2013/02/28/%EB%A9%80%ED%8B%B0-%EC%93%B0%EB%A0%88%EB%93%9C-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B9%88-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD/

:

스프링 multi sourcedata 사용

FRAMEWORK/SPRING 2013. 8. 23. 16:21

Spring-2.5.6 에서 여러개의 DataSource를 사용하는 방법을 소개한다.


다음과 같이 두개의 데이터 소스가 선언되어 있을 때 자동 주입(Inject)하고자 한다면 
<context:component-scan base-package="com.kyobobook.kflow" />

  <!-- ===================================================================== -->
  <!-- Transaction Configuration                                             -->
  <!-- ===================================================================== -->
  <aop:config proxy-target-class="true" />

  <bean id="riUniTestDataSource"  class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <qualifier value="riUniTestDataSource" />
    <property name="driverClassName" value="com.sybase.jdbc3.jdbc.SybDriver" />
    <property name="url" value="jdbc:sybase:Tds:192.168.0.15:3000" />
    <property name="connectionProperties" value="charset=eucksc;" />
    <property name="username" value="dev_kf" />
    <property name="password" value="dev_kf" />
  </bean>

  <bean id="riDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <qualifier value="riDataSource" />
    <property name="driverClassName" value="com.sybase.jdbc3.jdbc.SybDriver" />
    <property name="url" value="jdbc:sybase:Tds:192.168.0.6:3000/biltis" />
    <property name="connectionProperties" value="charset=eucksc;" />
    <property name="username" value="dev_kb" />
    <property name="password" value="dev_kb" />
  </bean>

첫번째 @Resource annotation을 사용할 수 있다.
    @Resource(name = "riDataSource")
    public void setDataSource(DataSource dataSource) {
        super.buildSqlMapClient(dataSource);
    }

두번째 방법은 @Autowired와 @Qualifier 를 사용하는 방법이다.
    @Autowired
    public void setDataSource(@Qualifier("riDataSource") DataSource dataSource) {
        super.buildSqlMapClient(dataSource);
    }

위 두가지 방법의 차이점은 DI(Dependency Injection)를 어떻게 할 것인가이다. 다음은 Anyframe에서 설명하는 Dependency Injection 에 대한 내용이다.(설명이 잘되어 있어서 퍼왔으나 라이센스가 문제가 된다면 링크로 변경할 것입니다.)

특정 Bean의 기능 수행을 위해 다른 Bean을 참조해야 하는 경우 사용하는 Annotation으로는 @Autowired 또는 @Resource가 있다.
  • @Autowired

  • Spring Framework에서 지원하는 Dependency 정의 용도의 Annotation으로, Spring Framework에 종속적이긴 하지만 정밀한 Dependency Injection이 필요한 경우에 유용하다.

  • @Resource

  • JSR-250 표준 Annotation으로 Spring Framework 2.5.* 부터 지원 가능한 Annotation이다. Annotation 사용으로 인해 특정 Framework에 종속적인 어플리케이션을 구성하지 않기 위해서는 @Resource를 사용할 것을 권장한다. @Resource를 사용하기 위해서는 클래스패스 내에 jsr250-api.jar 파일이 추가되어야 함에 유의해야 한다.
다음은 @Resource를 사용한 예이다.
@Service
public UserServiceImpl implements UserService {
    @Resource
    private UserDAO userDAO;
}

@Autowired와 @Resource를 사용할 수 있는 위치는 다음과 같이 약간의 차이가 있으므로 필요에 따라 적절히 사용하면 된다.
  • @Autowired : 필드, 생성자, 입력파라미터가 여러개인 메소드(@Qualifier는 메소드의 파라미터)에 적용 가능
  • @Resource : 필드, 입력 파라미터가 한 개인 빈 프로퍼티 setter 메소드에 적용가능
@Autowired나 @Resource를 필드에 직접 정의하는 경우 별도 setter 메소드는 정의하지 않아도 된다.


Type-driven Injection

@Autowired는 기본적으로 type-driven injection 이다. 타입으로 참조할 빈을 찾았을 때 같은 타입의 빈이 여러 개 검색되었을 경우, @Qualifier annotation을 사용하여 구분할 수 있도록 해준다. 

다음은 @Qualifier를 사용한 예이다.
@Service
public ProductService {
    @Autowired
    @Qualifier("electronics")
    private ProductCategory productCategory;
}
Qualifier를 정의하는 방법에는 다음과 같이 두가지가 있다.
  • XML을 사용한 정의
  • <bean class="anyframe.sample.springmvc.annotation.web.
    SimpleProductCategory">
        <qualifier value="electronics"/>
        <!-- inject any dependencies required by this bean -->
    </bean>
    
    <bean class="anyframe.sample.springmvc.annotation.web.
    SimpleProductCategory">
        <qualifier value="cosmetics"/>
        <!-- inject any dependencies required by this bean -->
    </bean>
    		
  • Annotation을 사용한 정의
  • @Component
    @Qualifier("electronics")
    public class ElectronicsProductCategory implements ProductCategory {
    	//...
    }

기본적으로 @Autowired가 적용된 프로퍼티는 필수이기 때문에 반드시 해당 빈이 존재해야 하지만, required 속성을 false로 설정하는 경우에는 해당되는 Bean을 찾지 못하더라도 에러가 발생하지 않는다.
@Service
public UserService implements UserService {
    @Autowired(required=false)
    private UserDAO userDAO;
}

Naming Auto Wired Dependencies

@Resource annotation은 다음과 같은 경우에 사용한다.
  • Bean name으로 Dependency Injection을 하고자 하는 경우
  • Type-driven injection을 할 수 없는 Collection이나 Map 타입의 빈
@Resource를 사용하는 경우 참조되는 Bean은 변수명을 Bean Name으로 하여 Spring 컨테이너에 의해 자동으로 인지되는데, 변수명을 이용하여 참조 관계에 놓인 Bean을 찾았는데 해당 Bean이 없는 경우에는 클래스 타입을 이용하게 된다. 참조 관계에 놓인 Bean의 Name을 직접 명시하고자 하는 경우에는 다음과 같이 'name' 속성을 부여할 수 있다.
@Service
public UserServiceImpl implements UserService {
    @Resource (name="uDAO")
    private UserDAO userDAO;
}

:

Spring Bean Life Cycle

FRAMEWORK/SPRING 2013. 8. 20. 18:05

The life cycle of a Spring bean is easy to understand. When a bean is instantiated, it may be required to perform some initialization to get it into a usable state. Similarly, when the bean is no longer required and is removed from the container, some cleanup may be required.

Though, there is lists of the activities that take place behind the scenes between the time of bean Instantiation and its destruction, but this chapter will discuss only two important bean lifecycle callback methods which are required at the time of bean initialization and its destruction.

To define setup and teardown for a bean, we simply declare the <bean> with init-method and/ordestroy-method parameters. The init-method attribute specifies a method that is to be called on the bean immediately upon instantiation. Similarly, destroy-method specifies a method that is called just before a bean is removed from the container.

Initialization callbacks:

The org.springframework.beans.factory.InitializingBean interface specifies a single method:

void afterPropertiesSet() throws Exception;

So you can simply implement above interface and initialization work can be done inside afterPropertiesSet() method as follows:

public class ExampleBean implements InitializingBean {
   public void afterPropertiesSet() {
      // do some initialization work
   }
}

In the case of XML-based configuration metadata, you can use the init-method attribute to specify the name of the method that has a void no-argument signature. For example:

<bean id="exampleBean" 
         class="examples.ExampleBean" init-method="init"/>

Following is the class definition:

public class ExampleBean {
   public void init() {
      // do some initialization work
   }
}

Destruction callbacks

The org.springframework.beans.factory.DisposableBean interface specifies a single method:

void destroy() throws Exception;

So you can simply implement above interface and finalization work can be done inside destroy() method as follows:

public class ExampleBean implements DisposableBean {
   public void destroy() {
      // do some destruction work
   }
}

In the case of XML-based configuration metadata, you can use the destroy-method attribute to specify the name of the method that has a void no-argument signature. For example:

<bean id="exampleBean" 
         class="examples.ExampleBean" destroy-method="destroy"/>

Following is the class definition:

public class ExampleBean {
   public void destroy() {
      // do some destruction work
   }
}

If you are using Spring's IoC container in a non-web application environment; for example, in a rich client desktop environment; you register a shutdown hook with the JVM. Doing so ensures a graceful shutdown and calls the relevant destroy methods on your singleton beans so that all resources are released.

It is recommended that you do not use the InitializingBean or DisposableBean callbacks, because XML configuration gives much flexibility in terms of naming your method.

Example:

Let us have working Eclipse IDE in place and follow the following steps to create a Spring application:

StepDescription
1Create a project with a name SpringExample and create a package com.tutorialspoint under the src folder in the created project.
2Add required Spring libraries using Add External JARs option as explained in the Spring Hello World Example chapter.
3Create Java classes HelloWorld and MainApp under the com.tutorialspoint package.
4Create Beans configuration file Beans.xml under the src folder.
5The final step is to create the content of all the Java files and Bean Configuration file and run the application as explained below.

Here is the content of HelloWorld.java file:

package com.tutorialspoint;

public class HelloWorld {
   private String message;

   public void setMessage(String message){
      this.message  = message;
   }
   public void getMessage(){
      System.out.println("Your Message : " + message);
   }
   public void init(){
      System.out.println("Bean is going through init.");
   }
   public void destroy(){
      System.out.println("Bean will destroy now.");
   }
}

Following is the content of the MainApp.java file. Here you need to register a shutdown hookregisterShutdownHook() method that is declared on the AbstractApplicationContext class. This will ensures a graceful shutdown and calls the relevant destroy methods.

package com.tutorialspoint;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
   public static void main(String[] args) {

      AbstractApplicationContext context = 
                          new ClassPathXmlApplicationContext("Beans.xml");

      HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
      obj.getMessage();
      context.registerShutdownHook();
   }
}

Following is the configuration file Beans.xml required for init and destroy methods:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean id="helloWorld" 
       class="com.tutorialspoint.HelloWorld"
       init-method="init" destroy-method="destroy">
       <property name="message" value="Hello World!"/>
   </bean>

</beans>

Once you are done with creating source and bean configuration files, let us run the application. If everything is fine with your application, this will print the following message:

Bean is going through init.
Your Message : Hello World!
Bean will destroy now.

Default initialization and destroy methods:

If you have too many beans having initialization and or destroy methods with the same name, you don't need to declare init-method and destroy-method on each individual bean. Instead framework provides the flexibility to configure such situation using default-init-method and default-destroy-methodattributes on the <beans> element as follows:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
    default-init-method="init" 
    default-destroy-method="destroy">

   <bean id="..." class="...">
       <!-- collaborators and configuration for this bean go here -->
   </bean>

</beans>



출처 - http://www.tutorialspoint.com/spring/spring_bean_life_cycle.htm

:

IBATIS 처리 후 Return 값

FRAMEWORK 2013. 8. 5. 12:52

insert()  

성공: null / 실패: 에러 발생

update()

성공: 적용된 row 수 / 실패: 0

delete() 

삭제한 row수 return


* insert태그의 경우 결과가 object 로 넘어오기 때문에 java에서 int형으로 결과를 받을 경우 null을 return 하게 된다.

따라서 insert시에도 update태그를 쓰는편이 사용하기도 편하고 원하는 결과를 얻기도 쉬우니 update태그에 insert쿼리를 쓰기도 한다.

'FRAMEWORK' 카테고리의 다른 글

라이브러리와 프레임워크에 대해  (0) 2016.12.20
Oracle Application Development Framework (ADF)  (0) 2016.12.12
Service Data Objects (SDO)  (0) 2016.12.12
Maven 을 이용한 프로젝트 생성 및 활용  (1) 2015.06.05
JNDI, TOMCAT  (0) 2013.11.06
:

springmodules의 valang을 활용한 Validation 테스트

FRAMEWORK/SPRING 2013. 7. 31. 15:18

Expert Spring MVC and Web Flow (Expert)에 소개된 springmodules의 valang을 활용하면 Validator를 자바 코딩 없이 선언만으로 구현할 수 있다.(XML 설정만으로 가능하다)

복잡한 자체적인 문법을 갖고 있어서 테스트 필요성이 높다. Expert Spring MVC and Web Flow (Expert)의 p281에는 테스트 예제가 있다.

public class PersonValidatorTests extends TestCase {
public void testEmptyPersonValidation() {
Person person = new Person();
Validator validator = new PersonValidator();
BindException errors = new BindException(person, "target");
validator.validate(person, errors);
new ErrorsVerifier(errors) {
{
forProperty("firstName").hasErrorCode("person.firstName.required")
.forProperty("lastName").hasErrorCode("person.lastName.required")
.otherwise().noErrors();
}
}
}
}


ErrorsVerifier를 헬퍼(Helper class)로 사용하여 간결하게 작성했지만 문제가 있다. ErrorsVerifier 클래스는 spring은 물론 spring-modules에도 존재하지 않는다는 점이다. 구글을 통해 Spring 포럼에 올라온 글, 'Where is ErrorsVerifier class mentioned in Expert Spring MVC?'을 확인할 수 있었다.

하지만, spring-modules의 테스트 및 소스 코드를 조금 참조하면 어렵지 않게 테스트를 작성하 수 있다.

public class MemberValidationTest extends TestCase {
private Member member;
private ValangValidator validator;
BindException errors;
@Override
protected void setUp() throws Exception {
member = new Member();
errors = new BindException(member, "member");
validator = new ValangValidator();
}
public void testPasswordValidation() throws Exception{
validator.setValang(
   "{ password : ? is not blank : '' : 'member.password.empty' }"
);
validator.afterPropertiesSet(); // configure validation rules
validator.validate(member, errors);

assertEquals("member.password.empty", errors.getFieldError("password ").getCode());
}
}


spring-modules의 테스트는 spring의 컨텍스트 파일(xml 형식의) 설정을 통해 테스트를 한다. 그러나, 단위 테스트를 목적으로 한다면 외부 리소스에 의존하는 일은 가능한 제거하는 것이 좋다고 생각하여 위와 같이 직접 setValang()을 호출한 것이다. setValang()은 실제로는 다음과 같은 식으로 설정해야 한다.

<bean id="easyAddressValidator" class="org.springmodules.validation.ValangValidator ">
  <property name="valang">
  <value><![CDATA[{ password : ? is not blank : '' : 'member.password.empty' }]]></value>
  </property>
</bean>


spring-module의 의존성에 따라 테스트를 실행하려면, 다음 라이브러리가 필요하다.

  • commons-collections.jar


assertion을 위한 코드가 늘어서는 것도 가독성을 크게 저해하지는 않는다. 책에서의 의도와 비슷하게 Fluent Interface 스타일을 적용해서 조금 수정해봤다.

public void testPasswordValidation() throws Exception{
validator.setValang(
   "{ password : ? is not blank : '' : 'member.password.empty' }"
);
validator.afterPropertiesSet(); // configure validation rules
validator.validate(member, errors);
new ErrorsVerifier(errors)
  .forProperty("password").hasErrorCode("member.password.empty");
}
class ErrorsVerifier{
private Errors errors;
private String cursor; // one of successive properties

public ErrorsVerifier(BindException errors) {
  this.errors = errors;
}
public ErrorsVerifier forProperty(String property) {
  cursor = property;
  return this;
}
public ErrorsVerifier hasErrorCode(String errorCode) {
  assertEquals(errorCode, errors.getFieldError(cursor).getCode());
  return this;
}

}


아쉬운 것은 정작 감춰야 할 부분인 아래 구문이 그대로 노출된다는 점이다:
validator.afterPropertiesSet();

감추기


추가적으로, valang 사용시 주의할 사항 0순위는 조건문이라고 생각된다. 마치 오른쪽과 왼쪽이 헷갈리는 것처럼 valang으로 기입한 조건이 만족하지 않으면 BindError가 발생한다는 사실을 유념해야 한다. 간단한 valang 설정 샘플을 첨부한다.

validator.setValang(
   "{ id : length(?) >= 3 : '' : 'member.id.tooshort' }" +
   "{ password : length(?) >= 4 : '' : 'member.password.tooshort' }" +
   "{ name : length(?) >= 2 : '' : 'member.name.tooshort' }" +
   "{ alias : ? is blank and length(?) >= 2 : '' : 'member.alias.tooshort' }"
);

또한, valang의 기본적으로 제공하는 함수 중에 앞의 예에서 보는 length()와 함께 email()이 있는데 주의할 점은 is true와 함께 써야 의도한대로 작동한다는 점이다.

"{ email : email(?) is true : '' : 'member.email.invaldformat' }" // 좋은 표현
"{ email : email(?) : '' : 'member.email.invaldformat' }" // parser exception 발생


감추기


감추기


원하는 함수가 없는 경우 직접 만들어서 사용할 수 있다. valang에 의존적인 클래스를 상속해야 하고 테스트가 무척 어렵기 때문에 가능하면 쓰지 않는 것이 좋을듯하나 편리하다.

이하 사용자 함수 작성
public class UrlFunction extends AbstractFunction {
public UrlFunction(Function[] arguments, int line, int column) {
 super(arguments, line, column);
}
/*
  * (non-Javadoc)
  *
  * @see org.springmodules.validation.valang.functions.AbstractFunction#doGetResult(java.lang.Object)
  */
@Override
protected Object doGetResult(Object target) throws Exception {
 String url = getArguments()[0].getResult(target).toString();
 if (url != null) {
  try {
   new URL(url);
   return true;
  } catch (MalformedURLException e) {
   return false;
  }
 }
 return false;
}
}

등록
validator.setValang(
   "{ blog : url(?) is true :  '' : 'member.blog.invaldformat' }"
 );
 validator.addCustomFunction("url", "net.webapp2.util.valang.UrlFunction");


감추기


출처 - http://ahnyounghoe.tistory.com/99

:

Spring Modules Validation

FRAMEWORK/SPRING 2013. 7. 31. 15:09

Spring Modules Validation

David Winterfeldt

2009


The Spring Modules project has a number of subprojects, including validation. This module is based on Spring ModulesValidation Version 0.9 and has a number of enhancements to the Valang part of the project.

Thanks to everyone that has worked on this project previously and currently.

1. Valang

Valang is short for Va-lidation Lang-uage. It provides a very readable language for expressing validation rules, as well as providing the ability to add custom functions to the language. Once the ValangValidator is configured, using it isn't different than any other Spring Validator since it implements org.springframework.validation.Validator.

Below is a list of current enhancements.

Version 0.91

  • Bytecode generation added to DefaultVisitor as a replacement for reflection accessing simple properties (BeanPropertyFunction) for a significant performance improvement.
  • Basic enum comparison support. In the expression below the personType is an enum and the value STUDENT will be convereted to an enum for comparison. The value must match an enum value on the type being compared or an exception will be thrown.
    personType EQUALS ['STUDENT']
    For better performance the full class name can be specified so the enum can be retrieved during parsing. The first example is for standard enum and the second one is for an inner enum class .
    personType EQUALS ['org.springmodules.validation.example.PersonType.STUDENT']
    personType EQUALS ['org.springmodules.validation.example.Person$PersonType.STUDENT']
  • Where clause support. In the expression below, the part of the expression price < 100 will only be evaluated if thepersonType is 'STUDENT'. Otherwise the validation will be skipped.
    price < 100 WHERE personType EQUALS ['STUDENT']
    [Note]Note

    Support for the where clause has not been added to the JavaScript custom tag currently.

  • Improved performance of 'IN'/'NOT IN' if comparing a value to a java.util.Set it will use Set.contains(value). Static lists of Strings (ex: 'A', 'B', 'C') are now stored in a Set instead of an ArrayList.
  • Functions can be configured in Spring, but need to have their scope set as prototype and use a FunctionWrapper that is also a prototype bean with <aop:scoped-proxy> set on it.
  • Removed servlet dependency from Valang project except for the custom JSP tag ValangValidateTag needing it, but running Valang no longer requires it. This involved removing ServletContextAware from it's custom dependency injection. If someone was using this in a custom function, the function can now be configured directly in Spring and Spring can inject any "aware" values.
  • Changed logging to use SLF4J api.

Version 0.92

  • Removed custom dependency injection since functions can be configured in Spring.
  • Added auto-discovery of FunctionWrapper beans from the Spring context to go with existing auto-discovery of FunctionDefinition beans.

Version 0.93

  • Made specific comparison classes for each operator for a performance improvement.
  • Changed IS WORD and IS BLANK to use Commons Lang StringUtils, which will change the behavior slightly but should be more accurate to the description of the validation.
  • Change Operator from interfaces to an enum and removed OperatorConstants.
  • Fixed bytecode generation to handle a Map, a List, and an Array.

Version 0.94

  • Upgraded to Spring 3.0 and changed group & artifact IDs to match standard Spring naming conventions.

Version 0.95

  • Upgraded to Spring 3.1 and minor improvements to bytecode generation.

Rule Syntax

The basic construction of a Valang rule is to have it begin and end with a brace. Within the braces, the default property name for the rule is specified first. Then the Valang expression, followed by the default error message. These are all the required values for a Valang rule. The other optional values for a rule are the error message key and arguments for it. Each of the values of the rule are delimitted by a colon.

{ <property-name> : <expression> : <default-error-message> : <error-message-key> : <error-message-args> }

Table 1. Rule Syntax

Rule ValueDescriptionRequired
property-nameThis is the default property of the bean being targeted for validation, and can be referred to with the shortcut ? in an expression.true
expressionThe Valang expression.true
default-error-messageThe default error message. If this isn't needed, it can be left blank even though it's required.true
error-message-keyThe message resource key for the i18n error message.false
error-message-argIf the error-message-key is specified, arguments for the error message can also be set as the final value of the rule. This accepts a comma delimited list of values.false

Expression Syntax

The expression language provides an English like syntax for expressing validation rules. There are a number of operators for comparing a value to another. Logical expressions, resulting in true or false, can be grouped together with parentheses to form more complex expressions.

Just to give some context to the explanation of all the rules, below is a simple example. The bean being validated has the properties getFirstName()getLastName(), and getAge(). The first two return a String and the last returns an int. The default property is 'firstName', which is referred to by the question mark. The first part of the rule enclosed in parentheses checks if the first name is either 'Joe' or it's length is greater than 5. The next part checks if the last name is one of the values in the list, and the final part checks if the age is over 18.

(? EQUALS 'Joe' OR length(?) > 5) AND lastName IN 'Johnson', 'Jones', 'Smith' AND age > 18

Operator Syntax

The parser is not case sensitive when processing the operators.

Table 2. Expression Operators

Comparison OperatorDescriptionSupportsExample
= | == | IS | EQUALSChecks for equality.Strings, booleans, numbers, dates, and enums.firstName EQUALS 'Joe'
!= | <> | >< | IS NOT | NOT EQUALSChecks for inequality.Strings, booleans, numbers, dates, and enums.firstName NOT EQUALS 'Joe'
> | GREATER THAN | IS GREATER THANChecks if a value is greater than another.Numbers and dates.age > 18
< | LESS THAN | IS LESS THANChecks if a value is less than another.Numbers and dates.age > 18
>= | => | GREATER THAN OR EQUALS | IS GREATER THAN OR EQUALSChecks if a value is greater than or equal to another.Numbers and dates.age >= 18
<= | =< | LESS THAN OR EQUALS | IS LESS THAN OR EQUALSChecks if a value is less than or equal to another.Numbers and dates.age <= 18
NULL | IS NULLChecks if a value is null.Objects.firstName IS NULL
NOT NULL | IS NOT NULLChecks if a value is not null.Objects.firstName IS NOT NULL
HAS TEXTChecks if the value has at least one non-whitespace character.Strings.firstName HAS TEXT
HAS NO TEXTChecks if the value doesn't have a non-whitespace character.Strings.firstName HAS NO TEXT
HAS LENGTHChecks if the value's length is greater than zero.Strings.firstName HAS LENGTH
HAS NO LENGTHChecks if the value's length is zero.Strings.firstName HAS NO LENGTH
IS BLANKChecks if the value is blank (null or zero length).Strings.firstName IS BLANK
IS NOT BLANKChecks if the value isn't blank (not null, length greater than zero).Strings.firstName IS NOT BLANK
IS UPPERCASE | IS UPPER CASE | IS UPPERChecks if the value is uppercase.Strings.firstName IS UPPERCASE
IS NOT UPPERCASE | IS NOT UPPER CASE | IS NOT UPPERChecks if the value isn't uppercase.Strings.firstName IS NOT UPPERCASE
IS LOWERCASE | IS LOWER CASE | IS LOWERChecks if the value is lowercase.Strings.firstName IS LOWERCASE
IS NOT LOWERCASE | IS NOT LOWER CASE | IS NOT LOWERChecks if the value isn't lowercase.Strings.firstName IS NOT LOWERCASE
IS WORDChecks if the value has one or more letters or numbers (no spaces or special characters).Strings.firstName IS WORD
IS NOT WORDChecks if the value doesn't have one or more letters or numbers (no spaces or special characters).Strings.firstName IS NOT WORD
BETWEENChecks if a value is between two other values.Numbers and dates.age BETWEEN 18 AND 65
NOT BETWEENChecks if a value isn't between two other values.Numbers and dates.age NOT BETWEEN 18 AND 65
INChecks if a value is in a list.Strings, booleans, numbers, dates, and enums.firstName IN 'Joe', 'Jack', 'Jane', 'Jill'
NOT INChecks if a value isn't in a list.Strings, booleans, numbers, dates, and enums.firstName NOT IN 'Joe', 'Jack', 'Jane', 'Jill'
NOTChecks for the opposite of the following expression.Any expression.NOT firstName EQUALS 'Joe'
!Changes a boolean expression to it's opposite.Booleansmatches('\\s+', firstName) IS !(TRUE)
ANDUsed to join together the logical comparisons on either side of the operator. Both must evaluate totrue.Any expression.firstName EQUALS 'Joe' AND age > 21
ORUsed to join together the logical comparisons on either side of the operator. Only one must evaluate totrue.Any expression.firstName EQUALS 'Joe' OR age > 21
WHEREIf the where expression is true, then the main expression for validation is performed. Otherwise it isn't evaluated and no errors are generated.Any expression.firstName EQUALS 'Joe' WHERE age > 21
thisA reference to the bean passed in for validation, which could be passed into a custom function for example.Any expression.isValid(this) IS TRUE

Literal Syntax

Table 3. Literals

Literal TypeDescriptionExample
StringString literals are surrounded by single quotes.'Joe'
NumbersNumbers can be expressed without any special syntax. Numbers are all parsed usingBigDecimal.1, 100, 0.73, -2.48
Dates

Date literals are surrounded by brackets.

These are the supported formats supported by the DefaultDateParser.

yyyyMMddyyyy-MM-ddyyyy-MM-dd HH:mm:ssyyyyMMdd HHmmssyyyyMMdd HH:mm:ss,yyyy-MM-dd HHmmss

[20081230], [2008-12-30], [2008-12-30 12:20:31]
BooleansThere are four different constants for boolean values. The values 'TRUE' and 'YES' represent true, and the values 'FALSE' and 'NO' represent falseTRUE, YES, FALSE, NO
EnumsEnums are surrounded by bracket and single quotes. If the full path to the enum isn't specified, it will be resolved when the expression is evaluated by looking up the enum value from enum on the opposite side of the expression.['FAIL'], ['org.springmodules.validation.valang.CreditStatus.FAIL'], ['org.springmodules.validation.valang.Person$CreditRating.EXCELLENT']

Mathematical Operator Syntax

Valang supports basic mathematical formulas based on numeric literals and property values.

Table 4. Mathematical Expression Operators

Mathematical OperatorDescriptionExample
+Addition operator.price + 12
-Subtraction operator.price - 12
*Multiplication operator.price * 1.2
/ | DIVDivision operator.price / 2
% | MODModulo operator.age % 10

Property Syntax

Valang supports standard property and nested property access to the bean passed in for validation.

Table 5. Property Syntax

Property TypeDescriptionExample
StandardUsing standard JavaBean property notation, a value from the bean being validated may be retrieved. The address represents getAddress() on the bean.address IS NOT NULL
NestedUsing standard JavaBean property notation, a nested value from the bean being validated may be retrieved. The address.city represents getAddress().getCity() on the bean.address.city IS NOT BLANK
ListFrom an array, List, or Set, a value from it can be returned by specifying it's index. Only arrays and lists are supported by bytecode generation.addresses[1] IS NOT NULL
MapFrom a Map, the value based on the key specified is retrieved.addresses[home] IS NOT NULL

Functions

These are built in functions that come with Valang. The function framework is pluggable, so it's easy to add custom functions. Adding custom functions will be covered in the next section.

Table 6. Functions

FunctionDescriptionExample
length | len | size | countReturns the size of a collection or an array, and otherwise returns the length of string by called toString() on the object.length(firstName) < 20
match | matchesPerforms a match on a regular expression. The first argument is the regular expression and the second is the value match on.matches('\\w+', firstName) IS TRUE
emailChecks if the value is a valid e-mail address.email(email) IS TRUE
upperConverts the value to uppercase.upper(firstName) EQUALS 'JOE'
lowerConverts the value to lowercase.lower(firstName) EQUALS 'joe'
resolveWraps a string in DefaultMessageSourceResolvable.resolve('personForm.firstName') EQUALS 'First Name'
inRoleChecks if the user authenticated by Spring Security is in a role.inRole('ADMIN') IS TRUE

Custom Functions

Custom functions can either be explicitly registered or instances of FunctionDefinition and FunctionWrapper are automatically registered with a ValangValidator. If just specifying a class name, it must have a constructor with the signature Function[] arguments, int line, int column. The FunctionWrapper is specifically for Spring configured functions. If the Function in a FunctionWrappertakes any arguments, it must implement ConfigurableFunction which allows the parser to configure the arguments, line number, and column number. Otherwise the line & column number will not be set on a Spring configured function.

[Note]Note

It's important for a FunctionWrapper around a custom Function to be of the scope prototype as well as theFunctionWrapper. Also the FunctionWrapper must have <aop:scoped-proxy/> defined so each call to it will get a new instance of the function. This is because as the validation language is parsed a new instance of a function is made each time and has the arguments specific to that function set on it.

Spring Configuration

The example below shows how to explicitly register a custom function directly with a validator. The custom functions 'validLastName' and 'creditApproval' are registered on the customFunctions property as a Map. The key is the name of the function to be used in the validation language and the value if the function being registered, which can either be the fully qualified name of the class or an instance of FunctionWrapper.

ValangValidatorCustomFunctionTest-context.xml
                        
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="creditApprovalFunction" 
          class="org.springmodules.validation.valang.CreditApprovalFunction"
          scope="prototype">
        <property name="creditRatingList">
            <list>
                <value>GOOD</value>
                <value>EXCELLENT</value>
            </list>
        </property>
    </bean>

    <bean id="personValidator" class="org.springmodules.validation.valang.ValangValidator">
        <property name="className" value="org.springmodules.validation.valang.Person"/>
        <property name="customFunctions">
            <map>
                <entry key="validLastName">
                    <value>org.springmodules.validation.valang.ValidLastNameFunction</value>
                </entry>
                <entry key="creditApproval">
                    <bean class="org.springmodules.validation.valang.functions.FunctionWrapper"
                          scope="prototype">
                        <aop:scoped-proxy/>
                        
                        <property name="function" ref="creditApprovalFunction" />
                    </bean>
                </entry>
            </map>
        </property>
        <!--
            Final validation tests that the aop:scoped-proxy is working since if the same instance 
            of CreditApprovalFunction is used it will be set to a failing value for both sides of the or.
            While if two instances are made the first condition should pass while the second will fail.
        -->
        <property name="valang">
            <value><![CDATA[
              { lastName : validLastName(?) is true : '' }
              { lastName : creditApproval(age, creditRating) is true : '' }
              { lastName : validLastName(?) is true AND creditApproval(age, creditRating) is true : '' }
              { lastName : validLastName(?) is true AND 
                           (creditApproval(age, creditRating) is true OR 
                            creditApproval(age, ['org.springmodules.validation.valang.Person$CreditRating.FAIR']) is true) : '' }
            ]]<</value>
        </property>
    </bean>
   
</beans>
                        
                    

Instances of FunctionDefinition and FunctionWrapper are automatically registered with a ValangValidator The custom functions 'validLastName' and 'creditApproval' are registered. If a FunctionWrapper doesn't have a function name specified, the name of the bean will be used for the function name.

ValangValidatorCustomFunctionDiscoveryTest-context.xml
                        
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springmodules.validation.valang.functions.FunctionDefinition"
          p:name="validLastName"
          p:className="org.springmodules.validation.valang.ValidLastNameFunction"/>

    <!-- Uses bean name for function name if not explicitly set on the wrapper -->
    <bean id="creditApproval" 
          class="org.springmodules.validation.valang.functions.FunctionWrapper"
          scope="prototype">
        <aop:scoped-proxy/>
        
        <property name="function">
            <bean id="creditApprovalFunction" 
                  class="org.springmodules.validation.valang.CreditApprovalFunction"
                  scope="prototype">
                <property name="creditRatingList">
                    <list>
                        <value>GOOD</value>
                        <value>EXCELLENT</value>
                    </list>
                </property>
            </bean>
        </property>
    </bean>
    
    <bean id="personValidator" class="org.springmodules.validation.valang.ValangValidator">
        <property name="className" value="org.springmodules.validation.valang.Person"/>
        <!--
            Final validation tests that the aop:scoped-proxy is working since if the same instance 
            of CreditApprovalFunction is used it will be set to a failing value for both sides of the or.
            While if two instances are made the first condition should pass while the second will fail.
        -->
        <property name="valang">
            <value><![CDATA[
              { lastName : validLastName(?) is true : '' }
              { lastName : creditApproval(age, creditRating) is true : '' }
              { lastName : validLastName(?) is true AND creditApproval(age, creditRating) is true : '' }
              { lastName : validLastName(?) is true AND 
                           (creditApproval(age, creditRating) is true OR 
                            creditApproval(age, ['org.springmodules.validation.valang.Person$CreditRating.FAIR']) is true) : '' }
            ]]<</value>
        </property>
    </bean>
   
</beans>
                        
                    

Code Example

Checks if the last name is in a list, and if it isn't false is returned.

Example 1. ValidLastNameFunction

                        
public class ValidLastNameFunction extends AbstractFunction {

    final Logger logger = LoggerFactory.getLogger(ValidLastNameFunction.class);
    
    final Set<String> lValidLastNames = new HashSet<String>();
    
    /**
     * Constructor
     */
    public ValidLastNameFunction(Function[] arguments, int line, int column) {
        super(arguments, line, column);
        definedExactNumberOfArguments(1);

        lValidLastNames.add("Anderson");
        lValidLastNames.add("Jackson");
        lValidLastNames.add("Johnson");
        lValidLastNames.add("Jones");
        lValidLastNames.add("Smith");
    }

    /**
     * Checks if the last name is blocked.
     * 
     * @return      Object      Returns a <code>boolean</code> for 
     *                          whether or not the last name is blocked.
     */
    @Override
    protected Object doGetResult(Object target) {
        boolean result = true;
        
        String symbol = getArguments()[0].getResult(target).toString();
        
        if (!lValidLastNames.contains(symbol)) {
            result = false;
        }
        
        return result;
    }
    
}
                        
                    

The function checks if a person can get credit approval. Their credit rating is checked against a list only if they are over 18 years old.

Example 2. ConfigurableFunction

                        
public class CreditApprovalFunction extends AbstractFunction 
        implements ConfigurableFunction {

    final Logger logger = LoggerFactory.getLogger(CreditApprovalFunction.class);
    
    Set<Person.CreditRating> lCreditRatings = new HashSet<Person.CreditRating>();

    /**
     * Constructor
     */
    public CreditApprovalFunction() {}

    /**
     * Constructor
     */
    public CreditApprovalFunction(Function[] arguments, int line, int column) {
        super(arguments, line, column);
        definedExactNumberOfArguments(2);
        
        lCreditRatings.add(Person.CreditRating.FAIR);
        lCreditRatings.add(Person.CreditRating.GOOD);
        lCreditRatings.add(Person.CreditRating.EXCELLENT);
    }

    /**
     * Gets number of expected arguments.
     * Implementation of <code>ConfigurableFunction</code>.
     */
    public int getExpectedNumberOfArguments() {
        return 2;
    }

    /**
     * Sets arguments, line number, and column number.
     * Implementation of <code>ConfigurableFunction</code>.
     */
    public void setArguments(int expectedNumberOfArguments, Function[] arguments,
                             int line, int column) {
        // important to set template first or can cause a NullPointerException 
        // if number of arguments don't match the expected number since 
        // the template is used to create the exception
        super.setTemplate(line, column);
        super.setArguments(arguments);
        super.definedExactNumberOfArguments(expectedNumberOfArguments);
    }
    
    /**
     * Sets valid credit rating approval list.
     */
    public void setCreditRatingList(Set<Person.CreditRating> lCreditRatings) {
        this.lCreditRatings = lCreditRatings;
    }

    /**
     * If age is over 18, check if the person has good credit, 
     * and otherwise reject.
     * 
     * @return      Object      Returns a <code>boolean</code> for 
     *                          whether or not the person has good enough 
     *                          credit to get approval.
     */
    @Override
    protected Object doGetResult(Object target) {
        boolean result = true;
        
        int age = (Integer) getArguments()[0].getResult(target);
        Person.CreditRating creditRating = (Person.CreditRating)getArguments()[1].getResult(target);

        // must be over 18 to get credit approval
        if (age > 18) {
            if (!lCreditRatings.contains(creditRating)) {
                result = false;
            }
        }
        
        return result;
    }
    
}
                        
                    

Bytecode Generation

If the validator will only be used to validate a specific class, the property 'className' can be specified to avoid reflection. If it's set, a custom Function will be generated that directly retrieves a property to avoid reflection. This provides a significant performance improvement if that is a concern, which typically isn't if the validation is being used to validate a web page since the delay is so small either way.

[Note]Note

Only a Map, a List, or an Array is supported by bytecode generation, not a Set. Primitive arrays currently aren't supported, but any object one is. Also, nested properties are currently not supported.

This is a small excerpt from the logging of the performance unit test. As you can see from the logging, as the validator is initialized it generates bytecode and shows for which class and method, as well as what the generated class name is. The package and name of the original class is used and then has what property is being retrieved appended along with 'BeanPropertyFunction$$Valang' to make a unique class name to try to avoid any collisions.

DefaultVisitor - Generated bytecode for org.springmodules.validation.valang.Person.getLastName() 
    as 'org.springmodules.validation.valang.PersonLastNameBeanPropertyFunction$$Valang'.
DefaultVisitor - Generated bytecode for org.springmodules.validation.valang.Person.getAge() 
    as 'org.springmodules.validation.valang.PersonAgeBeanPropertyFunction$$Valang'.
DefaultVisitor - Generated bytecode for org.springmodules.validation.valang.Person.getCreditRating() 
    as 'org.springmodules.validation.valang.PersonCreditRatingBeanPropertyFunction$$Valang'.
DefaultVisitor - Generated bytecode for org.springmodules.validation.valang.Person.getFirstName() 
    as 'org.springmodules.validation.valang.PersonFirstNameBeanPropertyFunction$$Valang'.
DefaultVisitor - Generated bytecode for org.springmodules.validation.valang.Person.getCreditStatus() 
    as 'org.springmodules.validation.valang.PersonCreditStatusBeanPropertyFunction$$Valang'.
ValangValidatorPerformanceTest - Took 7098.0ns.
ValangValidatorPerformanceTest - Took 2124.0ns.
ValangValidatorPerformanceTest - Message validator took 7098.0ns, and bytecode message valdiator took 2124.0ns.
                

Results from ValangValidatorPerformanceTest which was run on a Macbook Pro (2.3GHz Intel Core i7 with 8 GB RAM with OS X 10.6.8) with Java 6. All the expressions are identical, but adjusted to either retrieve the values being compared from a JavaBean, MapList, or an array.

Table 7. Bytecode Generation Performance Comparison

ExpressionReflectionBytcode Generation
{ lastName : validLastName(?) is true AND creditApproval(age, creditRating) is true WHERE firstName IN 'Joe', 'Jack', 'Jill', 'Jane' AND creditStatus IN ['org.springmodules.validation.valang.CreditStatus.PENDING'], ['org.springmodules.validation.valang.CreditStatus.FAIL'] AND creditRating EQUALS ['org.springmodules.validation.valang.Person$CreditRating.EXCELLENT'] AND age > 18 : '' }1176ns327ns
{ mapVars[lastName] : validLastName(?) is true AND creditApproval(mapVars[age], mapVars[creditRating]) is true WHERE mapVars[firstName] IN 'Joe', 'Jack', 'Jill', 'Jane' AND mapVars[creditStatus] IN ['org.springmodules.validation.valang.CreditStatus.PENDING'], ['org.springmodules.validation.valang.CreditStatus.FAIL'] AND mapVars[creditRating] EQUALS ['org.springmodules.validation.valang.Person$CreditRating.EXCELLENT'] AND mapVars[age] > 18 : '' }905ns48ns
{ listVars[1] : validLastName(?) is true AND creditApproval(listVars[2], listVars[4]) is true WHERE listVars[0] IN 'Joe', 'Jack', 'Jill', 'Jane' AND listVars[3] IN ['org.springmodules.validation.valang.CreditStatus.PENDING'], ['org.springmodules.validation.valang.CreditStatus.FAIL'] AND listVars[4] EQUALS ['org.springmodules.validation.valang.Person$CreditRating.EXCELLENT'] AND listVars[2] > 18 : '' }575ns43ns
{ vars[1] : validLastName(?) is true AND creditApproval(vars[2], vars[4]) is true WHERE vars[0] IN 'Joe', 'Jack', 'Jill', 'Jane' AND vars[3] IN ['org.springmodules.validation.valang.CreditStatus.PENDING'], ['org.springmodules.validation.valang.CreditStatus.FAIL'] AND vars[4] EQUALS ['org.springmodules.validation.valang.Person$CreditRating.EXCELLENT'] AND vars[2] > 18 : '' }563ns40ns

Spring Configuration

By specifying the 'className' property, bytecode will be generated for each method being called to avoid reflection. This gives a significant performance improvement.

Excerpt from ValangValidatorCustomFunctionTest-context.xml

                        
<!-- 
    Only perform validation if valid first name, credit status is failed or pending, 
    and the credit rating is excellent where the person's age is over 18.
-->
<bean id="expression" class="java.lang.String">
    <constructor-arg>
        <value><![CDATA[
          { lastName : validLastName(?) is true AND creditApproval(age, creditRating) is true
                       WHERE firstName IN 'Joe', 'Jack', 'Jill', 'Jane' AND
                             creditStatus IN ['org.springmodules.validation.valang.CreditStatus.PENDING'], 
                                             ['org.springmodules.validation.valang.CreditStatus.FAIL'] AND
                             creditRating EQUALS ['org.springmodules.validation.valang.Person$CreditRating.EXCELLENT'] AND
                             age > 18 : '' } 
        ]]<</value>
    </constructor-arg>
</bean>

...

<bean id="bytecodePersonValidator" class="org.springmodules.validation.valang.ValangValidator">
    <property name="className" value="org.springmodules.validation.valang.Person"/>
    <property name="valang" ref="expression" />
</bean>
                        
                    

Date Examples

The default date parser provides support for a number of different date literals, and also has support for shifting and manipulating dates. Below are a few examples, but see the DefaultDateParser for more detailed information.

Spring Configuration

ValangValidatorDateTest-context.xml
                        
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="personValidator" class="org.springmodules.validation.valang.ValangValidator">
        <property name="className" value="org.springmodules.validation.valang.Person"/>
        <property name="valang">
            <!-- 
                Third to last validation shifts '2008-12-30<y' to '2008-01-01 00:00:00'
                Second to last validation shifts '2005-04-09 23:30:00<M+10d+8H' to '2005-04-11 08:00:00'.
                Last shifts '2009-02-06 00:00:00<M+20y' to '2029-02-01 00:00:00'.
            -->
            <value><![CDATA[            
              { lastUpdated : ? > [20081230] : '' }
              { lastUpdated : ? > [2008-12-30] : '' }
              { lastUpdated : ? > [2008-12-30 12:20:31] : '' }
              { lastUpdated : ? > [20081230 122031] : '' }
              { lastUpdated : ? > [20081230 12:20:31] : '' }
              { lastUpdated : ? > [2008-12-30 122031] : '' }
              
              { lastUpdated : ? BETWEEN [20081230] AND [2009-02-06 00:00:00<M+20y] : '' }
              
              { lastUpdated : ? > [2008-12-30<y] : '' }
              { lastUpdated : ? > [2005-04-09 23:30:00<M+10d+8H] : '' }
              { lastUpdated : ? < [2009-02-06 00:00:00<M+20y] : '' }
            ]]<</value>
        </property>
    </bean>
   
</beans>
                        
                    


: