ibatis를 사용하면 보통 xml에 SQL을 작성하거나 아니면 JPA처럼 ORM을 사용한다.
그런데 코드 분석 중 메서드 주석에 @SQL 구문을 발견하였다.
/**
* @SQL
<![CDATA[
SELECT *
FROM USER
]]>
*/
public List<T> listUser(Map param) {
//...
}
|
처음에는 단순 주석인 줄 알았다.(가끔은 DAO 메서드에서 실행되는 SQL을 찾아 가기가 귀찮을 때가 있기 때문에… 물론 IDE plugin들이 많아서 그 귀찮음이 많이 없어지긴 했지만 말이다.)
단순 주석은 아닌 것 같아서 SqlMap 설정을 살펴 보았다. 눈에 띄는 것이 DocletSqlMapClientFactoryBean 이다.
@Bean(name = "sqlMapClientFactoryBean")
public DocletSqlMapClientFactoryBean docletSqlMapClientFactoryBean(
@Value("classpath:sqlmap/sqlmap-config.xml") Resource configLocation,
@Value("classpath*:com/**/*DAO.java") Resource[] mappingLocations) {
DocletSqlMapClientFactoryBean sqlMapClientFactoryBean = new DocletSqlMapClientFactoryBean();
sqlMapClientFactoryBean.setDataSource(dataSource(null));
sqlMapClientFactoryBean.setConfigLocation(configLocation);
sqlMapClientFactoryBean.setMappingLocations(mappingLocations);
return sqlMapClientFactoryBean;
}
|
DocletSqlMapClientFactoryBean 이 클래스가 SQL을 파싱하여 DB에 쿼리를 수행할 수 있도록 해 주는 넘인 듯 하다.
어떻게 SQL을 파싱할 수 있을까? 궁금해서 무작정 클래스를 열어 보았다.
public String parseSource(JavaClass javaClass) throws NestedIOException {
boolean isDAO = false;
Annotation[] arr$ = javaClass.getAnnotations();
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
Annotation annotation = arr$[i$];
if(annotation.getType().getJavaClass().isA(SqlMap.class.getName())) {
isDAO = true;
break;
}
}
if(!isDAO) {
return null;
} else {
if(this.log.isDebugEnabled()) {
this.log.debug(javaClass.getName() + " is a annotated SQLMAP DAO bean");
}
Sqlmap sqlMap = new Sqlmap();
String namespace = javaClass.getFullyQualifiedName();
sqlMap.setNamespace(namespace);
JavaMethod[] arr$ = javaClass.getMethods();
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
JavaMethod javaMethod = arr$[i$];
DocletTag sqlTag = javaMethod.getTagByName("SQL");
if(sqlTag != null) {
if(this.log.isDebugEnabled()) {
this.log.debug("parsing SQL : " + javaClass.getName() + "#" + javaMethod.getName());
}
String methodNm = javaMethod.getName();
Statement stmt = null;
if(!methodNm.startsWith("list") && !methodNm.startsWith("select")) {
if(methodNm.startsWith("count")) {
stmt = new Select();
((Statement)stmt).setResultClass("int");
} else if(methodNm.startsWith("update")) {
stmt = new Update();
} else if(methodNm.startsWith("delete")) {
stmt = new Delete();
} else if(methodNm.startsWith("insert")) {
stmt = new Insert();
} else if(methodNm.startsWith("sql")) {
stmt = new Sql();
}
} else {
stmt = new Select();
((Statement)stmt).setResultClass(ColumnToJavaNameConversionMap.class.getName());
if(methodNm.startsWith("select") && javaMethod.getReturns().isPrimitive()) {
((Statement)stmt).setResultClass(javaMethod.getReturns().getValue());
}
}
if(stmt == null) {
throw new NestedIOException("invalid statement for " + namespace + "#" + methodNm);
}
String locInfo = "/* " + javaClass.getFullyQualifiedName() + "." + javaMethod.getName() + "(" + javaClass.getName() + ".java:" + javaMethod.getLineNumber() + ") */\n";
String xml = "<statement>" + locInfo + sqlTag.getValue() + "\n/* END " + javaClass.getName() + "." + javaMethod.getName() + " */ </statement>";
try {
Statement tmpStmt = (Statement)this.unmar.unmarshal(new StringReader(xml));
((Statement)stmt).getContent().addAll(tmpStmt.getContent());
} catch (JAXBException var21) {
throw new NestedIOException("invalid sqlmap statement for " + namespace + "#" + methodNm + "\n" + SQLFORM.formatSQLAsString(sqlTag.getValue().replace("<![CDATA[", "/*CDATA*/").replace("]]>", "/*CDATA_END*/").replace("<", "/*STAG").replace(">", "ETAG*/").replaceAll("#([^#]*)#", "'_SHARP_$1_SHARP_END_'")).replace("/*CDATA*/", "<![CDATA[").replace("/*CDATA_END*/", "]]>").replace("/*STAG", "<").replace("ETAG*/", ">").replaceAll("'_SHARP_(.*)_SHARP_END_'", "#$1#") + "\n", var21);
}
((Statement)stmt).setId(methodNm);
ArrayList<String> args = new ArrayList();
JavaParameter[] arr$ = javaMethod.getParameters();
int len$ = arr$.length;
for(int i$ = 0; i$ < len$; ++i$) {
JavaParameter javaParameter = arr$[i$];
args.add(javaParameter.getName());
}
String[] paramnames = new String[args.size()];
this.sqlMapClientWrapper.addParameter(namespace + "." + methodNm, (String[])args.toArray(paramnames));
if(args.size() > 0) {
((Statement)stmt).setParameterClass("java.util.Map");
}
if(stmt != null) {
sqlMap.getStatements().add(stmt);
}
}
}
StringWriter writer = new StringWriter();
this.transformer.setOutputProperty("indent", "yes");
try {
this.transformer.transform(new JAXBSource(this.context, sqlMap), new StreamResult(writer));
} catch (Exception var20) {
throw new NestedIOException("error from " + javaClass.getFullyQualifiedName() + ":" + var20.getMessage(), var20);
}
return writer.toString();
}
}
|
뭔가 복잡해 보이긴 하지만 로직은 간단하다.
메서드 주석에 작성된 @SQL 구문을 파싱하여 Sql로 사용한다. 단 몇 가지 규약이 있다.(필히 지켜져야 하는 항목)
- 맴버 속석 중 SqlMap 인터페이스를 구현한 클래스가 존재해야 한다.
클래스 레벨에 @SqlMap 을 넣어 주어도 되고, SqlMapClientTemplate 같은 빈을 주입해 두어도 된다. - Select를 위해서는 메서드 이름이 list 또는 select로 시작해야 한다.
- 그 외에는 update, delete, insert로 시작하고 나머지는 Sql로 프로시저 같은 단순 수행 sql을 의미한다.
- <![CDATA[ ]]>로 꼭 묵어주자. 아니어도 상관없지만 비교 구문인 <, >에서 파싱 오류가 발생할 수 있다.
결론
XML로만 SQL을 지정할 수 있는 줄로만 알고 있었는데, @SQL을 이용하여 클래스 내에 주석을 활용할 수 있는 방법도 있다는 것을 처음 알게 되었다. 아직은 어떤 이점이 있는지 알지 못하겟지만 method만 봐도 어떤 SQL이 수행될 지 알 수 있는 편리성이 있는 것만 같다.
다만 메서드 명명 규칙에 규약이 있기 때문에 프로젝트 개발시 코딩 컨벤션은 잘 고려를 해야 할 것 같다.
'Programing > Spring' 카테고리의 다른 글
Ajax 호출 시 @ResponseBody로 한글을 내려줄 때 ???? 로 깨지는 현상 (0) | 2017.11.24 |
---|---|
Spring에서 Json을 파라메터로 넘겼을 때 어떻게 객체로 받을 수 있을까? (0) | 2017.07.21 |
MyBatis 를 이용하여 executeBatch 처리하기 (0) | 2016.06.22 |
Spring Bean 생성 및 주입 방법 (0) | 2015.06.23 |
@Resource VS @Autowired (0) | 2015.03.12 |