- Repository: 저장소라고도 하며 프로젝트의 소스와 소스의 변경사항들이 이 저장소 안에 저장된다. 네트워크를 통하여 여러 사람이 접근 가능하며, 버전 관리 시스템마다 각각의 저장소 포맷을 가지고 있으며 Subversion은 FSFS와 Berkeley DB 두가지 타입을 지원한다. 한 프로젝트마다 하나의 저장소가 필요하다.
- Checkout: 저장소에서 소스를 받아오는 것. 체크아웃을 한 소스를 보면 소스와 관련없는 디렉토리와 파일이 같이 내려오는 걸 볼 수 있는데, 이것들은 버전관리를 위해서 필요한 파일임으로 임의로 지우거나 변경하면 안된다.
- Commit: Checkout한 소스를 수정, 파일의 추가, 삭제를 한 뒤에 이를 Repository(저장소)에 반영 하는 것으로, Commit을 하면 Subversion의 전체 리비전이 1 증가하게 된다. 참고로 CVS는 수정한 파일의 리비전이 증가함.
- Update: Checkout을 해서 소스를 가져왔더라도 다른 사람이 Commit을 했다면 소스가 달라졌을 것이고, 이럴 경우 Update를 하면 Repository로부터 최신 버전의 소스를 가져온다. 물론 바뀐 부분만 가져온다.Revision: 소스를 수정한 뒤 Commit 할 때마다 일정한 규칙에 의하여 버전 번호가 증가한다. Repository에 저장된 각각의 파일 버전이라고 할 수 있다. Subversion인 경우 파일별로 Revision이 매겨지지 않고 한번 Commit 한 것으로 전체 Revision이 증가한다. 따라서 이 Revision을 보고 프로젝트의 진행 상황을 알 수 있다.
- Import: 빈 Repository에 맨 처음 소스를 넣는 작업이다.
- Export: Checkout과 달리 버전관리를 위한 파일을 뺀 순수한 소스 파일만을 내려받는다.
Subversion 서브버전 기본 용어 정리
Subversion 서브버전 배워보기
언제나 그렇듯이 새로운 툴을 배우고 사용한다는 건 긴장되고 설레인다. 아마 나이가 하나 둘 더 먹어가면서 긴장되는 쪽에 더 가까워 지는 건 어쩔 수 없는 모양이다.
어째든 그동안 정들었던 Microsoft Visual SourceSafe (MS VSS)를 버리고 Subversion 으로 갈아 탈 때가 된 것 같아, 간단히 SVN에 대하여 정리를 해 보기로 하자.
Subverion (SVN)은 2000년 CollabNet Inc 에 의해서 시작된 버전관리시스템이다. CVS나 Visual Sourcesafe를 사용해 본적이 있는 사용자들은 쉽게 어떤 소프트웨어인지 감이 잡힐 것이다. 다시 말하면 소스코드나 문서의 변경사항을 기록, 관리 할 수 있도록 해주는 시스템이다.
SVN은 오픈소스 진영에서 널리 사용되고 있는데 Apache Software Foundation, KDE, GNOME, FreeBSD, Python, Ruby 등등이 대표적인 예이다.
현재 SourceForge.net과 Tigris.org 에서 오픈 소스 프로젝트를 위한 Subversion 호스팅을 지원하고 있다.
Subversion은 Apache License 에 따라 배포되고 있으며 무료로 사용할 수 있다.
>Layers
Subversion은 내부적으로 몇가지 계층으로 나눠진 라이브러리들로 구성되어 있다 (사실 안 그런 시스템이 있으라만은). 각각의 계층은 아래와 같이 특정한 업무를 수행한다.
- Fs: 가장 최하위 계층으로 사용자의 데이타(소스코드나 문서)의 저장을 담당하는 파일시스템이다.
- Repos: Fs 위에 구축된 저장소 (repository). Fs 와 Repos가 FileSyste Interface를 구성한다.
- mod_dav_svn: Apache 2를 통한 WebDAV/Delta-V를 지원한다.
- 참고로 WebDAV는 Web-based Distributed Authoring and Versioning 의 약자로 여러 사용자가 웹상에서 떨어져 있는 컴퓨터의 파일을 같이 편집, 관리할 수 있도록 고안된 HTTP 프로토콜의 확장판이라고 보면된다. Delta-V는 이 WebDAV의 확장으로 파일의 변경내력을 관리하는 기능이 추가된 프로토콜이다.
- Ra: Repository Access. 리소스에 대한 로컬 또는 리모트(원격) 접근을 관리한다. 이 layer를 통하여 저장소는 URLs형태로 접근 가능하게 된다. 예를 들면 file:///path/ 를 통한 로컬 접근, http://host/path 또는 svn://host/path 은 원격 접근 URL 형식이다.
- Client, WC: 가장 최상위 레벨로. 저장소에 대한 접근과 SVN 사용자에 대한 서비스를 담당한다. 예를들면, 사용자 인증, 버전 비교 등등.

- trunk: 프로젝트의 가장 중심이 되는 디렉토리입니다. 참고로 trunk의 단어뜻은 여행용 가방이 아니라 :-) 식물의 줄기, 동물의 몸통을 뜻한다. 단어뜻에서 감이 오다시피 모든 프로젝트 작업은 이 trunk 디렉토리에서 이루어지게 된다. 다시 말하면 trunk 디렉토리 아래에 소스파일과 디렉토리가 들어가게 된다.
- branches: 나무가지. 개발자들이 반길 일은 아니지만 프로그램을 개발하다 보면 소스를 분기하여 따로 개발할 필요가 생긴다. 일종의 프로젝트안의 프로젝트라고 생각하면 된다. branches 디렉토리 안에 또 다른 디렉토리를 두어 그 안에서 개발하게 된다.
- tags: 꼬리표. 이 디렉토리는 프로그램을 개발하면서 정기적으로 릴리즈를 할 때 1.0, 2.5, 3.0 하는 식으로 버전 번호를 붙이게 되는데 그때 발표한 소스를 따로 저장하는 디렉토리이다.
- Commit 단위가 파일이 아니라 체인지셋이다. CVS는 여러개의 파일을 한꺼번에 commit하더라도 각각 파일마다 Revision이 따로 붙었다. 반면 Subversion은 파일별 revision이 없고 한 번 commit할 때마다 변경사항별로 revision 번호가 증가한다.
- CVS에 비해 속도가 빠른 Updating, Branching, Tagging
- CVS와 사용법이 같다.
- 파일 이름변경, 이동, 디렉토리 버전 관리도 지원
- Atomic commit 지원. Subversion은 여러개의 파일을 commit할때 실패 시 모두 이전 상태로 되돌린다.
- 트리별, 파일별 접근 제어 리스트 ACL. Repository 쓰기 접근 권한을 가진 개발자라도 아무 소스나 수정하지 못하게 조절할 수 있다.
- Repository/프로젝트별 환경 설정 가능


Branching 은 어떤 독립된 개발 프로젝트에 대한 소스변경을 기존 프로젝트 소스코드에 영향을 주지 않고 분리하여 작업할 수 있게 해준다.
Tagging은 추가 적인 정보를 설정할 수 있게 해주는 기능이다.
새 Branch 또는 tag 는 'svn copy' 명령어에 의해서 생성되는데, Subversion은 전체 소스를 모두 복사해서 만들지 않는다. 대신 예전버전과 새버전은 서로 내부적으로 연결(link)되어 있고, 변경 내력도 서로 공유한다. 따라서 새로운 Branch 또는 tag라도 실제 사이즈는 크지 않게 되는데 이는 Subversion이 단지 변경된 부분만을 저장하기 때문이다.
각각 branch의 모든 파일은 복사가 이뤄질때까지의 소스 변경 내력도 같이 갖게 되는데. branch 이후 변경 사항은 향후 다시 trunk 나 다른 branch들에 반영할 수 있다.
Subversion에서 branches와 tags의 차이는 소스 코드 변경사항이 tags에는 반영될 수 없다는 점이다.
Labels: 서브버전
아파치 톰캣 디버그 모드로 띄우기
- go to bin directory
- Open catalina.bat or catalinia.sh
- Add SET JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,address=8001,suspend=n,server=y -Dcom.sun.management.jmxremote
iBatis Support for Array or List Parameter with SQL IN Keyword
This is a feature available in iBatis but it is not mentioned in the documentation. You can find the example in the iBatis source code under the unit tests.
Let’s said I need to run the following SQL statement
select * from my_table where col_1 in ('1','2','3')
So how do I pass in the values of 1, 2 and 3 ?
In this case you need to pass in a list parameter. The correct iBatis syntax should be
< id="select-test" resultmap="MyTableResult" parameterclass="list">
select * from my_table where col_1 in
< open="(" close=")" conjunction=",">
#[]#
< /iterate >
< /select >
And in Java you should pass in a java.util.List. E.g.
Listlist = new ArrayList (3);
list.add("1");
list.add("2");
list.add("3");
List objs = sqlMapClient.queryForList("select-test",list);
This is another example
< id="getProducts" parameterclass="Product" resultclass="Product">
SELECT * FROM Products
< prepend="WHERE productType IN ">
< property="productTypes" open="(" close=")" conjunction=",">
productType=#productType#
< /iterate>
< /dynamic>
Implementing Serializable
Do not implement Serializable lightly, since it restricts future flexibility, and publicly exposes class implementation details which are usually private. As well, implementing Serializable correctly is not trivial.
The serialVersionUID is a universal version identifier for a Serializable class. Deserialization uses this number to ensure that a loaded class corresponds exactly to a serialized object. If no match is found, then an InvalidClassException is thrown.
Guidelines for serialVersionUID :
- always include it as a field, for example: "private static final long serialVersionUID = 7526472295622776147L; " include this field even in the first version of the class, as a reminder of its importance
- do not change the value of this field in future versions, unless you are knowingly making changes to the class which will render it incompatible with old serialized objects
- new versions of Serializable classes may or may not be able to read old serialized objects; it depends upon the nature of the change; provide a pointer to Sun's guidelines for what constitutes a compatible change, as a convenience to future maintainers
- use Control Panel | System | Environment to set the classpath to the correct directory
- run serialver -show from the command line
- point the tool to the class file including the package, for example, finance.stock.Account - without the .class
- (here are the serialver docs for both Win and Unix)
- readObject implementations always start by calling default methods
- deserialization must be treated as any constructor : validate the object state at the end of deserializing - this implies that readObject should almost always be implemented in Serializable classes, such that this validation is performed.
- deserialization must be treated as any constructor : if constructors make defensive copies for mutable object fields, so must readObject
- when serializing a Collection, store the number of objects in the Collection as well, and use this number to read them back in upon deserialization; avoid tricks using null
- use javadoc's @serial tag to denote Serializable fields
- the .ser extension is conventionally used for files representing serialized objects
- no static or transient fields undergo default serialization
- extendable classes should not be Serializable, unless necessary
- inner classes should rarely, if ever, implement Serializable
- container classes should usually follow the style of Hashtable, which implements Serializable by storing keys and values, as opposed to a large hash table data structure
import java.io.Serializable;
import java.text.StringCharacterIterator;
import java.util.*;
import java.io.*;
public final class SavingsAccount implements Serializable {
/**
* This constructor requires all fields to be passed as parameters.
*
* @param aFirstName contains only letters, spaces, and apostrophes.
* @param aLastName contains only letters, spaces, and apostrophes.
* @param aAccountNumber is non-negative.
* @param aDateOpened has a non-negative number of milliseconds.
*/
public SavingsAccount (
String aFirstName, String aLastName, int aAccountNumber, Date aDateOpened
){
super();
setFirstName(aFirstName);
setLastName(aLastName);
setAccountNumber(aAccountNumber);
//make a defensive copy of the mutable Date passed to the constructor
setDateOpened( new Date(aDateOpened.getTime()) );
//there is no need here to call validateState.
}
public SavingsAccount () {
this ("FirstName", "LastName", 0, new Date(System.currentTimeMillis()));
}
public final String getFirstName() {
return fFirstName;
}
public final String getLastName(){
return fLastName;
}
public final int getAccountNumber() {
return fAccountNumber;
}
/**
* Returns a defensive copy of the field.
* The caller may change the state of the returned object in any way,
* without affecting the internals of this class.
*/
public final Date getDateOpened() {
return new Date(fDateOpened.getTime());
}
/**
* Names must contain only letters, spaces, and apostrophes.
* Validate before setting field to new value.
*
* @throws IllegalArgumentException if the new value is not acceptable.
*/
public final void setFirstName( String aNewFirstName ) {
validateName(aNewFirstName);
fFirstName = aNewFirstName;
}
/**
* Names must contain only letters, spaces, and apostrophes.
* Validate before setting field to new value.
*
* @throws IllegalArgumentException if the new value is not acceptable.
*/
public final void setLastName ( String aNewLastName ) {
validateName(aNewLastName);
fLastName = aNewLastName;
}
/**
* Validate before setting field to new value.
*
* @throws IllegalArgumentException if the new value is not acceptable.
*/
public final void setAccountNumber( int aNewAccountNumber ) {
validateAccountNumber(aNewAccountNumber);
fAccountNumber = aNewAccountNumber;
}
public final void setDateOpened( Date aNewDate ){
//make a defensive copy of the mutable date object
Date newDate = new Date( aNewDate.getTime());
validateDateOpened( newDate );
fDateOpened = newDate;
}
// PRIVATE //
/**
* The client's first name.
* @serial
*/
private String fFirstName;
/**
* The client's last name.
* @serial
*/
private String fLastName;
/**
* The client's account number.
* @serial
*/
private int fAccountNumber;
/**
* The date the account was opened.
* @serial
*/
private Date fDateOpened;
/**
* Determines if a de-serialized file is compatible with this class.
*
* Maintainers must change this value if and only if the new version
* of this class is not compatible with old versions. See Sun docs
* for details.
*
* Not necessary to include in first version of the class, but
* included here as a reminder of its importance.
*/
private static final long serialVersionUID = 7526471155622776147L;
/**
* Verify that all fields of this object take permissible values; that is,
* this method defines the class invariant.
*
* In this style of implementation, both the entire state of the object
* and its individual fields can be validated without repeating or
* duplicating code.
* Each condition is defined in one place. Checks on the entire
* object are performed at the end of object construction, and at
* the end of de-serialization. Checks on individual fields are
* performed at the start of the corresponding setXXX method.
* As well, this style replaces the if's and throwing
* of exceptions at the start of a setXXX, with a simple call to validateXXX.
* Validation is separated from the regular path of execution,
* which leads to improved legibility.
*
* @throws IllegalArgumentException if any field takes an unpermitted value.
*/
private void validateState() {
validateAccountNumber(fAccountNumber);
validateName(fFirstName);
validateName(fLastName);
validateDateOpened(fDateOpened);
}
/**
* Ensure names contain only letters, spaces, and apostrophes.
*
* @throws IllegalArgumentException if field takes an unpermitted value.
*/
private void validateName(String aName){
boolean nameHasContent = (aName != null) && (!aName.equals(""));
if (!nameHasContent){
throw new IllegalArgumentException("Names must be non-null and non-empty.");
}
StringCharacterIterator iterator = new StringCharacterIterator(aName);
char character = iterator.current();
while (character != StringCharacterIterator.DONE ){
boolean isValidChar =
(Character.isLetter(character) ||
Character.isSpaceChar(character) ||
character =='\''
);
if ( isValidChar ) {
//do nothing
}
else {
String message = "Names can contain only letters, spaces, and apostrophes.";
throw new IllegalArgumentException(message);
}
character = iterator.next();
}
}
/**
* AccountNumber must be non-negative.
* @throws IllegalArgumentException if field takes an unpermitted value.
*/
private void validateAccountNumber(int aAccountNumber){
if (aAccountNumber < 0) {
String message = "Account Number must be greater than or equal to 0.";
throw new IllegalArgumentException(message);
}
}
/**
* DateOpened must be after 1970.
* @throws IllegalArgumentException if field takes an unpermitted value.
*/
private void validateDateOpened( Date aDateOpened ) {
if( aDateOpened.getTime()<0 ) {
throw new IllegalArgumentException("Date Opened must be after 1970.");
}
}
/**
* Always treat de-serialization as a full-blown constructor, by
* validating the final state of the de-serialized object.
*/
private void readObject(
ObjectInputStream aInputStream
) throws ClassNotFoundException, IOException {
//always perform the default de-serialization first
aInputStream.defaultReadObject();
//make defensive copy of the mutable Date field
fDateOpened = new Date( fDateOpened.getTime() );
//ensure that object state has not been corrupted or tampered with maliciously
validateState();
}
/**
* This is the default implementation of writeObject.
* Customise if necessary.
*/
private void writeObject(
ObjectOutputStream aOutputStream
) throws IOException {
//perform the default serialization for all non-transient, non-static fields
aOutputStream.defaultWriteObject();
}
}