-
Notifications
You must be signed in to change notification settings - Fork 3
GettingStarted_KO
freemarker-dynamic-ql-builder ๋ฅผ ์ฌ์ฉํ๋ฉด Java์์ ๋์ ์ผ๋ก SQL, JPQL, HQL ๋ฑ์ ์์ฑํ๊ณ JDBC ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์ธ๋ฉํด์ค ์ ์๋ค. ์ด์ ๋ถํฐ ๋์ค๋ QL์ SQL, JPQL, HQL ๋ฑ์ ์๋ฏธํ๋ค.
์ด๋ ๊ฒ ์์ฑํ ์ฟผ๋ฆฌ ๋ฌธ์์ด๊ณผ ๋ฐ์ธ๋ฉ ํ๋ผ๋ฏธํฐ List(ํน์ ๋ฐฐ์ด)์ Plain PreparedStatement๋ SpringFramework JdbcTemplate, jDBI, JPA, Hibernate ๋ฑ์์ Native ํน์ JPQL/HQL์ ์คํํ๋ ์ฉ๋๋ก ์ฌ์ฉํ ์ ์๋ค.
- Java 6+
- ์์กด์ฑ์ด freemarker 2.3.23+์ slf4j ๋จ ๋๊ฐ๋ฟ์ธ ๊ฐ๋ฒผ์ด ๋์ ์ฟผ๋ฆฌ ์์ฑ ํด์ด๋ค.
compile 'kr.pe.kwonnam.freemarkerdynamicqlbuilder:freemarker-dynamic-ql-builder:{version}'<dependency>
<groupId>kr.pe.kwonnam.freemarkerdynamicqlbuilder</groupId>
<artifactId>freemarker-dynamic-ql-builder</artifactId>
<version>{version}</version>
</dependency>๋จผ์ ํ๋ฆฌ๋ง์ปค Configuration ๊ฐ์ฒด๋ฅผ ์
๋ง๋๋ก ์์ฑํ๊ณ , ์ด๋ฅผ ์ฌ์ฉํ์ฌ FreemarkerDynamicQlBuilder ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
์ต์ข
์์ฑ๋ FreemarkerDynamicQlBuilder ๊ฐ์ฒด๋ฅผ SpringFramework Bean์ผ๋ก ๋ฑ๋กํด์ Spring๊ณผ ํจ๊ป ์ฌ์ฉํด๋ ๋๋ค.
FreemarkerDynamicQlBuilder๋ Thread Safe ํ๋ค. ๋จ Configuration ๊ฐ์ฒด๋ฅผ ํ ๋ฒ ์ค์ ํ ์ดํ ๋ณ๊ฒฝํด์๋ ์๋๋ค.
import freemarker.template.Configuration;
import kr.pe.kwonnam.freemarkerdynamicqlbuilder.FreemarkerDynamicQlBuilder;
import kr.pe.kwonnam.freemarkerdynamicqlbuilder.FreemarkerDynamicQlBuilderFactory;
// ...
// ์์ ์ด ์ํ๋ ๋๋ก Freemarker ์ค์ ์ ํด์ค๋ค.
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
cfg.setClassForTemplateLoading(AbstractFreemarkerDynamicQlBuilderTest.class, "/META-INF/dynamicqls");
cfg.setDefaultEncoding("UTF-8");
// ์ซ์ํ์ ์ผํ์์ด ์ซ์๋ง ์ถ๋ ฅํ๊ฒ ํฌ๋ฏธํ
cfg.setNumberFormat("0.######");
// ํ
ํ๋ฆฟ ํ์ผ ๋ณ๊ฒฝ ์ฒดํฌ ์ฃผ๊ธฐ๋ฅผ 1์๊ฐ์ผ๋ก ์ค์
cfg.setTemplateUpdateDelayMilliseconds(3600000L); // ms ๋จ์
// ์ฑ๋ฅ ํฅ์์ ์ํ ์บ์ ์ค์
cfg.setCacheStorage(new MruCacheStorage(500, 5000));
// Freemarker ์ค์ ์ ๊ฐ์ง๊ณ FreemarkerDynamicQlBuilder ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
FreemarkerDynamicQlBuilder dynamicQlBuilder = new FreemarkerDynamicQlBuilderFactory(cfg)
// ์ค๊ฐ์ ๊ธฐํ ์ค์ ๋ค..
.getFreemarkerDynamicQlBuilder();๋์ ์ผ๋ก QL์ ์์ฑํ๋ ค๋ฉด ํ๋ฆฌ๋ง์ปค Configuration ์์ ์ง์ ํ ๊ฒฝ๋ก์์ ๋๋ ํ ๋ฆฌ๋ฅผ ๋ง๋ค๊ณ ํ์ผ์ ํ์ฅ์๊ฐ .ql.ftl๋ก ๋๋๊ฒ ์์ฑํด์ค๋ค.
์์ ์ค์ ์์๋ CLASSPATH ๋ด์ /META-INF/dynamicqls ๋๋ ํ ๋ฆฌ๋ฅผ ํ
ํ๋ฆฟ ํ์ผ๋ก ์ง์ ํ์์ผ๋ฏ๋ก /META-INF/dynamicqls/users/select.ql.ftl ์ ์์ฑํ๋ค๊ณ ๊ฐ์ฃผํ๋ฉด,
SELECT *
FROM somewhere
<@ql.where>
<#if user.name?has_content>
name = ${param(user.name)}
</#if>
<#if user.birthyear gt 0>
AND birthyear = ${param(user.birthyear)}
</#if>
<#if user.employeeType??>
AND employeeType = ${param(user.employeeType, 'enumToName')}
</#if>
<#list userIds!>
AND userId IN (<#items as userId>${param(userId)}<#sep>,</#sep></#items>)
</#list>
</@ql.where>
ORDER BY userId
LIMIT 10์ด์ Java ์ฝ๋์์์๋
User user = new User();
user.setName(""); // empty on purpose
user.setBirthyear(2015);
user.setEmployeeType(EmployeeType.FULLTIME);
Map<String,Object> dataModel = new HashMap<String,Object>();
dataModel.put("user", user);
dataModel.put("userIds", new int[]{100, 200, 300});
DynamicQuery dynamicQuery = = dynamicQlBuilder.buildQuery("users/select", dataModel);
// dynamicQuery ์ ์์ฑ๋ QL๊ณผ ํ๋ฆฌ๋จธํฐ ๋ชฉ๋ก์ด ๋ค์ด ์๋ค.์ด ๋ช
๋ น์ ์คํํ๊ณ ๋์ dynamicQuery ๊ฐ์ฒด๋ฅผ ์ดํด๋ณด๋ฉด ์ฟผ๋ฆฌ ์คํ์ ํ์ํ SQL ๋ฌธ์์ด๊ณผ, Positional Parameter(๋ฌผ์ํ, ?)์ ๋ฐ์ธ๋ฉ ๋ ํ๋ผ๋ฏธํฐ ๊ฐ์ฒด๋ค์ ๋ฆฌ์คํธ ํน์ ๋ฐฐ์ด์ ์ป์ ์ ์๊ฒ ๋๋ค.
dynamicQuery.getQueryString()
==> String
"SELECT *
FROM somewhere
WHERE birthyear = ?
AND employeeType = ?
AND userId IN (?,?,?)
ORDER BY userId
LIMIT 10"
dynamicQuery.getParameters()
==> List<Object> : [2015, FULLTIME, 100, 200, 300]
dynamicQuery.getQueryParameterArray()
==> Object[] : [2015, FULLTIME, 100, 200, 300]
์ด์ ์ด๊ฒ์ ๊ฐ์ง๊ณ PreparedStatement์ ๋ฐฐ์ด์ for๋ฌธ ๋๋ฉด์ ๋ฐ์ธ๋ฉํ๊ฑฐ๋ ์๋์ ๊ฐ์ด ๊ฐํธํ๊ฒ ๋ฐ์ธ๋ฉ ํ ์ ๋ ์๋ค.
PreparedStatement psmt = connection.prepareCall(dynamicQuery.getQueryString());
dynamicQuery.bindParameters(psmt);
ResultSet rs = psmt.executeQuery();
// ...Plain JDBC๊ฐ ์๋ SpringJdbcTemplate์ด๋ jDBI, Hibernate, JPA ๋ฑ์ ์ฟผ๋ฆฌ์ ๋ฐ์ธ๋ฉํ ํ๋ผ๋ฏธํฐ๋ฅผ ๋๊ฒจ์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ ์๋ ์๋ค.
๋์ ์ฟผ๋ฆฌ ์์ฑ์ ํต์ฌ์ ๋์ ์ผ๋ก ๋ฌธ์์ด์ ๋ง๋๋ ๊ฒ์ด ์๋๋ผ, ๋์ ์ผ๋ก ์์ฑ๋ ๋ฌธ์์ด์ JDBC ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ์ด๋ป๊ฒ ๋งค์นญ์ํค๋๋์ ์๋ค.
MyBatis์์ #{๋ณ์} ์ญํ ์ ํ๋ ๊ฒ์ด ์ฌ๊ธฐ์ ${param(๋ณ์)}์ด๋ค.
${param(๋ณ์)}
์ ๊ตฌ๋ฌธ์ ์ค์ ๋ก๋ Prepared Statement Positional Parameter์ธ ๋ฌผ์ํ(?) ํ๋๋ง ์ถ๋ ฅํ๋ค.
?
ํ์ง๋ง, ์ด์ ๋์์ ๋ณ์์ ๊ฐ์ด ์์๋๋ก ์ ์ฅ๋์ด ๋์ QL ์์ฑ์ด ์ข
๋ฃ๋๋ฉด DynamicQuery.getParameters() ๋ก๋ java.util.List<Object> ๊ฐ์ฒด ํํ๋ฅผ, DynamicQuery.getParameterArray()๋ก๋ ๋์ผ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ฒด ๋ฐฐ์ด(Object[]) ํํ๋ฅผ ์ป์ ์ ์๊ฒ ๋๋ค.
์ด๋ ๊ฒ ์์ฑ๋ ํ๋ผ๋ฏธํฐ ๋ฆฌ์คํธ/๋ฐฐ์ด์ ๊ฐ์ง๊ณ PreparedStatement ์ ์์๋๋ก ํ๋ผ๋ฏธํฐ๋ก ์ง์ ํ๋ฉด ๋๋ค.
Plain JDBC PreparedStatement๋ DynaimcQuery.bindParameters(PreparedStatement preparedStatement) ๋ฉ์๋๋ฅผ ํตํด ๋ฐ์ธ๋ฉ์ ์๋์ผ๋ก ํด์ค ์๋ ์๋ค.
JPA์ JPQL๊ณผ Hibernate์ HQL์ ?์ธ๋ฑ์ค ํํ๋ก ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ ํ๋ ๊ฒ์ด ํ์ค์ด๋ค. HQL์ ?๋ ์ง์ํ๊ธด ํ์ง๋ง ์์ผ๋ก ํด๋น ํ๋ผ๋ฏธํฐ ํํ๋ ์ญ์ ํ ์์ ์ด๋ผ๊ณ ํ๋ค.
FreemarkerDynamicQlBuilder#buildQuery()์ ๋ง์ง๋ง ์ธ์(withPositionalIndex)๋ฅผ true๋ก ์ง์ ํ๋ฉด ๋ชจ๋ ํ๋ผ๋ฏธํฐ ๋ค์ ์ซ์๊ฐ ๋ถ๊ฒ ๋๋ค.
DynamicQuery dynamicQuery = dynamicQlBuilder.buildQuery(queryTemplateName, dataModel, true);์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ด ํ ํ๋ฆฟ์ ์์ฑํ์๋ค๋ฉด
${param(๋ณ์1)}, ${param(๋ณ์2)}, ${param(๋ณ์3)}์ถ๋ ฅ๋ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ ํํ๊ฐ ๋๋ค.
?1, ?2, ?3ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ ํ์ง ์๊ณ ๋ณ์์ ๋ฌธ์์ด ํํ๋ฅผ ์ง์ ์ถ๋ ฅํ๋ ๊ฒ์ ํ๋ฆฌ๋ง์ปค์ ๊ธฐ๋ณธ ๋ณ์ ์ถ๋ ฅ ๋ฐฉ์์ ์ฌ์ฉํ๋ค.
${๋ณ์}๋ณ์์ ๋ด์ฉ์ด ๋ฌธ์์ด๋ก ์๋ ๊ทธ๋๋ก ์ถ๋ ฅ๋๋ค. ๋ฌผ๋ก ์ด ๊ฒฝ์ฐ, SQL Injection ๋ฑ์ ์ํ์ด ์๊ธฐ ๋๋ฌธ์ ์ฒ ์ ํ๊ฒ ๊ฒ์ฆ๋ ๋ฐ์ดํฐ์ ํํด์๋ง ์ด ๋ฐฉ์์ ์ฌ์ฉํด์ผ ํ๋ค. ๋ณดํต์ ๊ฒฝ์ฐ์๋ ์ฌ์ฉํด์๋ ์๋๋ค.
ํ๋ฆฌ๋ง์ปค Configuration ๊ฐ์ฒด ์ค์ ์์ cfg.setNumberFormat("0.######")์ ํธ์ถํ์ง ์์ผ๋ฉด ์ซ์๊ฐ ์ผํ๊ฐ ์ฐํ ํํ(123,456,789)๋ก ์ถ๋ ฅ๋์ด SQL ๋ฌธ๋ฒ์ ์ด๊ธ๋๊ฒ ๋๋ฏ๋ก ๋งค์ฐ ์ฃผ์ํด์ผ ํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ๋น์ฐํ ๋ชจ๋ ํ๋ฆฌ๋ง์ปค ์ ํ ํ๋ฆฟ ๊ตฌ๋ฌธ์ ์ฌ์ฉํ ์ ์๋ค. ์ด์ ๊ดํด์๋ Freemarker Manual ์ ์ฐธ์กฐํ๋ค.
ํ๋ฆฌ๋ง์ปค์ ๊ธฐ๋ณธ ์ง์์ ์ธ์๋, MyBatis์ ์ง์์๋ค์ ํ๋ด๋ด์ด ์ฟผ๋ฆฌ ์์ฑ์ ํธ๋ฆฌํ๊ฒ ๋์์ฃผ๋๋ก ํ์๋ค.
MyBatis์ <where>์ ๊ฐ์ ์ญํ ์ ํ๋ค. SQL์ ๋์ WHERE ์กฐ๊ฑด ์์ฑ์ ์ฌ์ฉํ๋ค. ์๋์ ๊ฐ์ ๊ตฌ๋ฌธ์ด ์์ ๋ ๋ชจ๋ ์ ๋ค ๊ณต๋ฐฑ์ ์ ๊ฑฐํ๊ณ ์ง์์ ์์ ๋ด์ฉ์ด
- ๊ณต๋ฐฑ์ด ์๋ ๋ฌธ์์ด์ ํฌํจํ๋ค๋ฉด
WHERE๋ฅผ ์ถ๊ฐํ๊ณ ์ฒ์ ๋์ค๋AND,and,OR,or๋ฅผ ์ญ์ ํ๋ค. - ๋ชจ๋ ๊ณต๋ฐฑ(๋น์นธ, ์์ค๊ธฐํธ ๋ฑ)์ด๋ผ๋ฉด ์๋ฌด๊ฒ๋ ์ถ๋ ฅํ์ง ์๋๋ค.
<@ql.where>
<#if user.name?has_content>
name = ${param(user.name)}
</#if>
<#if user.birthyear gt 0>
AND birthyear = ${param(user.birthyear)}
</#if>
<#if user.employeeType??>
AND employeeType = ${param(user.employeeType, 'enumToName')}
</#if>
</@ql.where>
==>
WHERE birthyear = ?
AND employeeType = ?์ด ๋ด์ฉ์ <@ql.trim>์ผ๋ก ํ ๊ฒฝ์ฐ ๋ค์๊ณผ ๋์ผํ๋ค.
<@ql.trim prefix="WHERE " prefixOverrides=["AND ", "and ", "OR ", "or "]>
๊ธฐํ ๋ด์ฉ๋ค
</@ql.trim>MyBatis์ <set>์ ๊ฐ์ ์ญํ ์ ํ๋ค. SQL์ ๋์ UPDATE๋ฌธ ์์ฑ์ ํจ๊ป ์ฌ์ฉํ๋ค. ์๋์ ๊ฐ์ ๊ตฌ๋ฌธ์ด ์์ ๋ ๋ชจ๋ ์ ๋ค ๊ณต๋ฐฑ์ ์ ๊ฑฐํ๊ณ ์ง์์ ์์ ๋ด์ฉ์ด
- ๊ณต๋ฐฑ์ด ์๋ ๋ฌธ์์ด์ ํฌํจํ๋ค๋ฉด
SET๋ฅผ ์ถ๊ฐํ๊ณ ๊ณต๋ฐฑ์ ์ ์ธํ๊ณ ๋งจ ๋ง์ง๋ง์ ๋์ค๋ ์ผํ(,)๋ฅผ ์ ๊ฑฐํ๋ค. - ๋ชจ๋ ๊ณต๋ฐฑ(๋น์นธ, ์์ค๊ธฐํธ ๋ฑ)์ด๋ผ๋ฉด ์๋ฌด๊ฒ๋ ์ถ๋ ฅํ์ง ์๋๋ค.
UPDATE sometable
<@ql.set>
<#if user.name??>name = ${param(user.name)},</#if>
<#if user.birthyear gt 0>birthyear = ${param(user.birthyear)},</#if>
<#if user.employeeType??>employeeType = ${para(user.employyType)}</#if>
</@ql.set>
==>
UPDATE sometable
SET name = ?,
birthyear = ?์ด ๋ด์ฉ์ <@ql.trim>์ผ๋ก ํ ๊ฒฝ์ฐ ๋ค์๊ณผ ๋์ผํ๋ค.
UPDATE sometable
<@ql.trim prefix="SET " suffixOverrides=[","]>
๊ธฐํ ๋ด์ฉ๋ค
</@ql.trim>MyBatis์ <trim>๊ณผ ๊ฐ์ ์ญํ ์ ํ๋ค. ๋ฒ์ฉ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค.
- ๊ธฐ๋ณธ์ ์ผ๋ก๋ ํด๋น ์ง์์ ์์ ์์ฑ๋ ๋ฌธ์์ด์ ์๋ค ๊ณต๋ฐฑ์ ๋ชจ๋ ์ ๊ฑฐํ๋ค.
-
prefix="๋ฌธ์์ด": trim ๊ฒฐ๊ณผ๊ฐ ๊ณต๋ฐฑ์ด ์๋ ๋ฌธ์์ด์ ํฌํจํ ๊ฒฝ์ฐ, ์์ฑ๋ ๋ฌธ์์ด ๋งจ ์์ ํด๋น ๋ฌธ์์ด์ ์ถ๊ฐํ๋ค. -
suffix="๋ฌธ์์ด": trim ๊ฒฐ๊ณผ๊ฐ ๊ณต๋ฐฑ์ด ์๋ ๋ฌธ์์ด์ ํฌํจํ ๊ฒฝ์ฐ, ์์ฑ๋ ๋ฌธ์์ด ๋งจ ๋ค์ ํด๋น ๋ฌธ์์ด์ ์ถ๊ฐํ๋ค. -
prefixOverrides=["๋ฌธ์์ด1", "๋ฌธ์์ด2"]: trim ๊ฒฐ๊ณผ๊ฐ ๊ณต๋ฐฑ์ด ์๋ ๋ฌธ์์ด์ ํฌํจํ ๊ฒฝ์ฐ, ์์ฑ๋ ๋ฌธ์์ด์ ๋งจ ์์ ๋ฐฐ์ด๋ก ์ง์ ๋ ๋ฌธ์์ด๋ค ์ค ํ๋๊ฐ ๋์ค๋ฉด ์ด๋ฅผ ์ญ์ ํ๋ค. ์ฒซ๋ฒ์งธ ๋งค์นญ๋ง ์ญ์ ํ๋ค. -
suffixOverrides=["๋ฌธ์์ด1", "๋ฌธ์์ด2"]: trim ๊ฒฐ๊ณผ๊ฐ ๊ณต๋ฐฑ์ด ์๋ ๋ฌธ์์ด์ ํฌํจํ ๊ฒฝ์ฐ, ์์ฑ๋ ๋ฌธ์์ด์ ๋งจ ๋ค์ ๋ฐฐ์ด๋ก ์ง์ ๋ ๋ฌธ์์ด๋ค ์ค ํ๋๊ฐ ๋์ค๋ฉด ์ด๋ฅผ ์ญ์ ํ๋ค. ์ฒซ๋ฒ์งธ ๋งค์นญ๋ง ์ญ์ ํ๋ค.
์์ ๋ <@ql.where>์ <@ql.set> ์ฐธ์กฐ.
MyBatis์๋ foreach ๊ตฌ๋ฌธ์ด ์๋๋ฐ, Freemarker๋ ์ด๋ฏธ ์ด์ ๊ฐ์ ์ญํ ์ ํ ์ ์๋ ํจ์ฌ ๋ ์ ์ฐํ ์ง์์๊ฐ ์กด์ฌํ๋ค. ๊ทธ๋์ foreach๋ ๋ฐ๋ก ๋ง๋ค์ง ์๊ณ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํํ๋ฉด ๋๋ค.
userIds = new int[] {1,2,3,4,5} ๋ผ๊ณ ํ ๋,
WHERE
<#list userIds!>
user_id in (<#items as userId>${param(userId)}<#sep>,</#sep></#items>)
<#else>
user_id IS NOT NULL
</#list>userIds์ ๊ฐ์ด ์กด์ฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋๋ค.
WHERE
user_id in (?, ?, ?, ?, ?)userIds๊ฐ null์ด๊ฑฐ๋ ๋น List ํน์ ๋ฐฐ์ด์ด๋ฉด <#else> ๊ตฌ๋ฌธ๋ค๊ฐ ์คํ๋๋ค.
WHERE
user_id IS NOT NULL๋ฌผ๋ก <#else>๋ ์๋ต ๊ฐ๋ฅํ๋ค.
์์ธํ ๊ฒ์ ํ๋ฆฌ๋ง์ปค ๋ฌธ์๋ฅผ ์ฐธ์กฐํ๋ค.