doortts doortts 2016-02-01
DB migration: Add exporting and importing feature
Add new feature that can be used for data migration.
Only the site-admin can export and import data.
Data exporting is downloading a file that contains json strings
are generated by data from database. Data importing is
truncating tables and inserting all data read from json strings
that is in a uploaded file.

CAUTION: TO KEEP DATA INTEGRITY, PLEASE PREVENT OTHER REQUESTS
AND DO NOT ANYTHING ELSE WHILE DOING EXPORT/IMPORT
RECOMMEND YOU THAT MAKE ENVIRONMENT THAT ONLY THE SITE-ADMIN CAN ACCESS.
e.g.) stop proxy server and run the yobi on a port that only you know.

Codes are based on mainly works of
  - Keesun Baik 
  - Yi EungJun 

Tested export and import DB and version

- H2 1.3.176 to MariaDB 10.1.10
- H2 1.3.176 to MySQM 5.6.28
@7ad78be926a0b25020ee0cd0ad4d9282611150a2
app/controllers/SiteApp.java
--- app/controllers/SiteApp.java
+++ app/controllers/SiteApp.java
@@ -21,7 +21,9 @@
 package controllers;
 
 import com.avaje.ebean.Page;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import controllers.annotation.AnonymousCheck;
+import data.DataService;
 import info.schleichardt.play2.mailplugin.Mailer;
 import models.*;
 import models.enumeration.State;
@@ -30,13 +32,20 @@
 import org.apache.commons.mail.EmailException;
 import org.apache.commons.mail.SimpleEmail;
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.springframework.format.datetime.DateFormatter;
 import play.Configuration;
 import play.Logger;
 import play.db.ebean.Transactional;
-import views.html.site.*;
-import play.mvc.*;
+import play.mvc.Controller;
+import play.mvc.Http;
+import play.mvc.Result;
+import play.mvc.With;
 import utils.*;
+import views.html.site.*;
 
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.*;
 
 import static play.libs.Json.toJson;
@@ -44,7 +53,7 @@
 /**
  * The Class SiteApp.
  */
- @With(SiteManagerAuthAction.class)
+@With(SiteManagerAuthAction.class)
 @AnonymousCheck
 public class SiteApp extends Controller {
 
@@ -279,4 +288,37 @@
     public static Result diagnose() {
         return ok(diagnostic.render("title.siteSetting", Diagnostic.checkAll()));
     }
+
+    public static Result data() {
+        return ok(data.render("title.siteSetting"));
+    }
+
+    public static Result exportData() throws JsonProcessingException {
+        Date date = new Date();
+        DateFormatter formatter = new DateFormatter("yyyyMMddHHmm");
+        String formattedDate = formatter.print(date, Locale.getDefault());
+
+        InputStream in = new DataService().exportData();
+        response().setContentType("application/x-download");
+        response().setHeader("Content-disposition","attachment; filename=yobi-data-" + formattedDate + ".json");
+
+        return ok(in);
+    }
+
+    public static Result importData() throws IOException {
+        Http.MultipartFormData body = request().body().asMultipartFormData();
+        Http.MultipartFormData.FilePart yobiData = body.getFile("data");
+        if (yobiData != null) {
+            File file = yobiData.getFile();
+            try {
+                new DataService().importData(file);
+                return redirect(routes.Application.index());
+            } catch (Exception e) {
+                return badRequest(ErrorViews.BadRequest.render());
+            }
+        } else {
+            return redirect(routes.SiteApp.data());
+        }
+    }
+
 }
 
app/data/DataService.java (added)
+++ app/data/DataService.java
@@ -0,0 +1,259 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import data.exchangers.*;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.springframework.dao.CleanupFailureDataAccessException;
+import org.springframework.jdbc.CannotGetJdbcConnectionException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import play.Configuration;
+import play.db.DB;
+
+import javax.sql.DataSource;
+import java.io.*;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @author Keeun Baik
+ */
+@Service
+public class DataService {
+
+    private List<Exchanger> exchangers;
+
+    private static final Comparator<Exchanger> COMPARATOR = new Comparator<Exchanger>() {
+        @Override
+        public int compare(Exchanger ex1, Exchanger ex2) {
+            return ex1.getTable().compareTo(ex2.getTable());
+        }
+    };
+
+    String dataSourceName;
+
+    public DataService() {
+        exchangers = new ArrayList<>();
+        exchangers.add(new AssigneeDataExchanger());
+        exchangers.add(new AttachmentDataExchanger());
+        exchangers.add(new CommentThreadDataExchanger());
+        exchangers.add(new CommentThreadUserDataExchanger());
+        exchangers.add(new CommitCommentDataExchanger());
+        exchangers.add(new EmailDataExchanger());
+        exchangers.add(new IssueCommentDataExchanger());
+        exchangers.add(new IssueCommentVoterDataExchanger());
+        exchangers.add(new IssueDataExchanger());
+        exchangers.add(new IssueEventDataExchanger());
+        exchangers.add(new IssueIssueLabelDataExchanger());
+        exchangers.add(new IssueLabelCategoryDataExchanger());
+        exchangers.add(new IssueLabelDataExchanger());
+        exchangers.add(new IssueVoterDataExchanger());
+        exchangers.add(new LabelDataExchanger());
+        exchangers.add(new MentionDataExchanger());
+        exchangers.add(new MilestoneDataExchanger());
+        exchangers.add(new NotificationEventDataExchanger());
+        exchangers.add(new NotificationEventUserDataExchanger());
+        exchangers.add(new NotificationMailDataExchanger());
+        exchangers.add(new OrganizationDataExchanger());
+        exchangers.add(new OrganizationUserDataExchanger());
+        exchangers.add(new OriginalEmailDataExchanger());
+        exchangers.add(new PostingCommentDataExchanger());
+        exchangers.add(new PostingDataExchanger());
+        exchangers.add(new ProjectDataExchanger());
+        exchangers.add(new ProjectLabelDataExchanger());
+        exchangers.add(new ProjectMenuDataExchanger());
+        exchangers.add(new ProjectPushedBranchDataExchanger());
+        exchangers.add(new ProjectTransferDataExchanger());
+        exchangers.add(new ProjectUserDataExchanger());
+        exchangers.add(new ProjectVisitationDataExchanger());
+        exchangers.add(new PropertyDataExchanger());
+        exchangers.add(new PullRequestCommitDataExchanger());
+        exchangers.add(new PullRequestDataExchanger());
+        exchangers.add(new PullRequestEventDataExchanger());
+        exchangers.add(new PullRequestReviewersDataExchanger());
+        exchangers.add(new RecentlyVisitedProjectsDataExchanger());
+        exchangers.add(new ReviewCommentDataExchanger());
+        exchangers.add(new RoleDataExchanger());
+        exchangers.add(new SiteAdminDataExchanger());
+        exchangers.add(new UnwatchDataExchanger());
+        exchangers.add(new UserDataExchanger());
+        exchangers.add(new UserEnrolledOrganizationDataExchanger());
+        exchangers.add(new UserEnrolledProjectDataExchanger());
+        exchangers.add(new UserProjectNotificationDataExchanger());
+        exchangers.add(new WatchDataExchanger());
+        Collections.sort(exchangers, COMPARATOR);
+        dataSourceName = Configuration.root().getString("ebeanconfig.datasource.default", "default");
+    }
+
+    public InputStream exportData() {
+        final DateTime start = DateTime.now();
+        DataSource dataSource = DB.getDataSource(dataSourceName);
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+        final String dbName = getDBName(dataSource);
+        final String catalogName = getCatalogName(dataSource);
+        ObjectMapper mapper = getObjectMapper();
+        final JsonFactory factory = mapper.getFactory();
+
+        PipedInputStream in = new PipedInputStream();
+        try {
+            final PipedOutputStream out = new PipedOutputStream(in);
+            new Thread(
+                new Runnable() {
+                    public void run () {
+                        try {
+                            JsonGenerator generator = factory.createGenerator(out, JsonEncoding.UTF8);
+                            generator.setPrettyPrinter(new DefaultPrettyPrinter());
+                            generator.writeStartObject();
+
+                            for (Exchanger exchanger : exchangers) {
+                                exchanger.exportData(dbName, catalogName, generator, jdbcTemplate);
+                            }
+
+                            generator.writeEndObject();
+                            generator.close();
+
+                            DateTime end = DateTime.now();
+                            Duration duration = new Duration(start, end);
+                            play.Logger.info("Data export took {{}}", duration.getStandardSeconds());
+                        }
+                        catch (IOException e) {
+                            play.Logger.error("Failed to export data");
+                        }
+                    }
+                }
+            ).start();
+            return in;
+        } catch (IOException e) {
+            play.Logger.error("Failed to export data");
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String getCatalogName(DataSource dataSource) {
+        Connection connection = null;
+        try {
+            connection = dataSource.getConnection();
+            return connection.getCatalog();
+        } catch (SQLException e) {
+            throw new CannotGetJdbcConnectionException("failed to get connection", e);
+        } finally {
+            if (connection != null) {
+                try { connection.close(); } catch (SQLException e) {
+                    throw new CleanupFailureDataAccessException("failed to close connection", e);
+                }
+            }
+        }
+    }
+
+    public void importData(File file) throws IOException {
+        DateTime start = DateTime.now();
+        ObjectMapper mapper = getObjectMapper();
+        JsonFactory factory = mapper.getFactory();
+        JsonParser parser = factory.createParser(file);
+
+        JsonToken current = parser.nextToken();
+        if (current != JsonToken.START_OBJECT) {
+            play.Logger.info("Data import failed cause of root if not an object.");
+            return;
+        }
+
+        DataSource dataSource = DB.getDataSource(dataSourceName);
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
+        String dbName = getDBName(dataSource);
+        disableReferentialIntegtiry(dbName, jdbcTemplate);
+        String message = "";
+        try {
+            for (Exchanger exchanger : exchangers) {
+                exchanger.importData(dbName, parser, jdbcTemplate);
+            }
+            message = "Data import done. it took {{}}";
+        } catch (Exception e) {
+            message = "Data import failed. it took {{}}";
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        } finally {
+            enableReferentialIntegrity(dbName, jdbcTemplate);
+            DateTime end = DateTime.now();
+            Duration duration = new Duration(start, end);
+            play.Logger.info(message, duration.getStandardSeconds());
+        }
+    }
+
+    private String getDBName(DataSource dataSource) {
+        Connection connection = null;
+        try {
+            connection = dataSource.getConnection();
+            DatabaseMetaData metaData = connection.getMetaData();
+            return metaData.getDatabaseProductName();
+        } catch (SQLException e) {
+            throw new CannotGetJdbcConnectionException("failed to get meta data", e);
+        } finally {
+            if (connection != null) {
+                try { connection.close(); } catch (SQLException e) {
+                    throw new CleanupFailureDataAccessException("failed to close connection", e);
+                }
+            }
+        }
+    }
+
+    private void enableReferentialIntegrity(String dbName, JdbcTemplate jdbcTemplate) {
+        if (dbName.equals("H2")) {
+            for (Exchanger exchanger : exchangers) {
+                jdbcTemplate.update("ALTER TABLE " + exchanger.getTable() + " SET REFERENTIAL_INTEGRITY TRUE");
+            }
+            jdbcTemplate.update("SET REFERENTIAL_INTEGRITY TRUE");
+        }
+    }
+
+    private void disableReferentialIntegtiry(String dbName, JdbcTemplate jdbcTemplate) {
+        if(dbName.equals("H2")) {
+            jdbcTemplate.update("SET REFERENTIAL_INTEGRITY FALSE");
+            for (Exchanger exchanger : exchangers) {
+                jdbcTemplate.update("ALTER TABLE " + exchanger.getTable() + " SET REFERENTIAL_INTEGRITY FALSE");
+            }
+        }
+    }
+
+    private static ObjectMapper getObjectMapper() {
+        ObjectMapper mapper = new ObjectMapper();
+        // prevent serializing null property to a json field
+        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        // use filed access only
+        mapper.setVisibilityChecker(mapper.getSerializationConfig().getDefaultVisibilityChecker()
+                .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
+                .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
+                .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
+                .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
+        return mapper;
+    }
+
+}
 
app/data/DefaultExchanger.java (added)
+++ app/data/DefaultExchanger.java
@@ -0,0 +1,378 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.commons.io.IOUtils;
+import org.springframework.jdbc.core.BatchPreparedStatementSetter;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowCallbackHandler;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import play.Configuration;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Keeun Baik
+ */
+public abstract class DefaultExchanger implements Exchanger {
+
+    private static final int DEFAULT_BATCH_SIZE = 100;
+    private final static String DATA_BATCH_SIZE_KEY = "data.batch.size";
+
+    protected Long timestamp(Timestamp timestamp) {
+        if (timestamp != null) {
+            return timestamp.getTime();
+        }
+        else {
+            return null;
+        }
+    }
+
+    protected Long date(Date date) {
+        if (date != null) {
+            return date.getTime();
+        }
+        else {
+            return null;
+        }
+    }
+
+    protected Timestamp timestamp(long time) {
+        if (time == 0l) {
+            return null;
+        } else {
+            return new Timestamp(time);
+        }
+    }
+
+    protected Date date(long time) {
+        if (time == 0l) {
+            return null;
+        } else {
+            return new Date(time);
+        }
+    }
+
+    protected void setNullableLong(PreparedStatement ps, short index, JsonNode node, String column) throws SQLException {
+        if (node.get(column).isNull()) {
+            ps.setNull(index, Types.BIGINT);
+        } else {
+            ps.setLong(index, node.get(column).longValue());
+        }
+    }
+
+    protected String clobString(@Nullable Clob clob) throws SQLException {
+        if (clob == null) {
+            return null;
+        }
+        Reader reader = clob.getCharacterStream();
+        StringWriter writer = new StringWriter();
+        try {
+            IOUtils.copy(reader, writer);
+            return writer.toString();
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    protected void setClob(PreparedStatement ps, short index, JsonNode node, String column) throws SQLException {
+        String value = node.get(column).textValue();
+        if (value == null) {
+            ps.setNull(index, Types.CLOB);
+        } else {
+            Clob clob = ps.getConnection().createClob();
+            clob.setString(1, value);
+            ps.setClob(index, clob);
+        }
+    }
+
+    /**
+     * generates VALUES part of a sql like, VALUES (?, ?, ?)
+     *
+     * @param size
+     * @return
+     */
+    protected String values(int size) {
+        String values = "VALUES (";
+        for(int i = 0 ; i < size - 1 ; i++) {
+            values += "?, ";
+        }
+        values += "?)";
+        return values;
+    }
+
+    protected void putLong(JsonGenerator generator, String fieldName, ResultSet rs, short index) throws SQLException, IOException {
+        generator.writeFieldName(fieldName);
+        long value = rs.getLong(index);
+        if (rs.wasNull()) {
+            generator.writeNull();
+        } else {
+            generator.writeNumber(value);
+        }
+    }
+
+    protected void putInt(JsonGenerator generator, String fieldName, ResultSet rs, short index) throws SQLException, IOException {
+        generator.writeFieldName(fieldName);
+        int value = rs.getInt(index);
+        if (rs.wasNull()) {
+            generator.writeNull();
+        } else {
+            generator.writeNumber(value);
+        }
+    }
+
+    protected void putString(JsonGenerator generator, String fieldName, ResultSet rs, short index) throws SQLException, IOException {
+        generator.writeFieldName(fieldName);
+        String string = rs.getString(index);
+        if (string == null) {
+            generator.writeNull();
+        } else {
+            generator.writeString(string);
+        }
+    }
+
+    protected void putBoolean(JsonGenerator generator, String fieldName, ResultSet rs, short index) throws SQLException, IOException {
+        generator.writeFieldName(fieldName);
+        generator.writeBoolean(rs.getBoolean(index));
+    }
+
+    protected void putTimestamp(JsonGenerator generator, String fieldName, ResultSet rs, short index) throws SQLException, IOException {
+        generator.writeFieldName(fieldName);
+        Timestamp timestamp = rs.getTimestamp(index);
+        if (timestamp == null) {
+            generator.writeNull();
+        } else {
+            generator.writeNumber(timestamp.getTime());
+        }
+    }
+
+    protected void putDate(JsonGenerator generator, String fieldName, ResultSet rs, short index) throws SQLException, IOException {
+        generator.writeFieldName(fieldName);
+        Date date = rs.getDate(index);
+        if (date == null) {
+            generator.writeNull();
+        } else {
+            generator.writeNumber(date.getTime());
+        }
+    }
+
+    protected void putClob(JsonGenerator generator, String fieldName, ResultSet rs, short index) throws SQLException, IOException {
+        generator.writeFieldName(fieldName);
+        String clobString = clobString(rs.getClob(index));
+        if (clobString == null) {
+            generator.writeNull();
+        } else {
+            generator.writeString(clobString);
+        }
+    }
+
+    public void exportData(String dbName, String catalogName, final JsonGenerator generator, JdbcTemplate jdbcTemplate) throws IOException {
+        generator.writeFieldName(getTable());
+        generator.writeStartArray();
+        final int[] rowCount = {0};
+        jdbcTemplate.query(getSelectSql(), new RowCallbackHandler() {
+            @Override
+            public void processRow(ResultSet rs) throws SQLException {
+                try {
+                    generator.writeStartObject();
+                    setNode(generator, rs);
+                    generator.writeEndObject();
+                    rowCount[0]++;
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        generator.writeEndArray();
+        play.Logger.info("exported {{}} {}", rowCount[0], getTable());
+
+        if (hasSequence()) {
+            String sequenceName = sequenceName();
+            long sequenceValue = 0;
+            if (dbName.equalsIgnoreCase("MySQL")) {
+                String sql = String.format("SELECT `AUTO_INCREMENT` FROM INFORMATION_SCHEMA.TABLES " +
+                        "WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'", catalogName, getTable());
+                sequenceValue = jdbcTemplate.queryForObject(sql, Long.class);
+            } else if (dbName.equalsIgnoreCase("H2")) {
+                sequenceValue = jdbcTemplate.queryForObject("CALL NEXT VALUE FOR " + sequenceName, Long.class);
+            }
+            generator.writeFieldName(sequenceName);
+            generator.writeNumber(sequenceValue);
+            play.Logger.info("exported sequence {{}}", sequenceName());
+        }
+    }
+
+    public void importData(String dbName, JsonParser parser, JdbcTemplate jdbcTemplate) throws IOException {
+        PlatformTransactionManager tm = new DataSourceTransactionManager(jdbcTemplate.getDataSource());
+        TransactionStatus ts = tm.getTransaction(new DefaultTransactionDefinition());
+
+        try {
+            if (dbName.equals("MySQL")) {
+                jdbcTemplate.update("SET FOREIGN_KEY_CHECKS = 0");
+                jdbcTemplate.update("SET NAMES \'utf8mb4\'");
+            }
+
+            final Configuration config = Configuration.root();
+            int batchSize = config.getInt(DATA_BATCH_SIZE_KEY, DEFAULT_BATCH_SIZE);
+            if (parser.nextToken() != JsonToken.END_OBJECT) {
+                String fieldName = parser.getCurrentName();
+                play.Logger.debug("importing {}", fieldName);
+                if (fieldName.equalsIgnoreCase(getTable())) {
+                    truncateTable(jdbcTemplate);
+                    JsonToken current = parser.nextToken();
+                    if (current == JsonToken.START_ARRAY) {
+                        importDataFromArray(parser, jdbcTemplate, batchSize);
+                        importSequence(dbName, parser, jdbcTemplate);
+                    } else {
+                        play.Logger.info("Error: records should be an array: skipping.");
+                        parser.skipChildren();
+                    }
+                }
+            }
+            tm.commit(ts);
+        } catch (Exception e) {
+            e.printStackTrace();
+            tm.rollback(ts);
+        } finally {
+            if (dbName.equals("MySQL")) {
+                jdbcTemplate.update("SET FOREIGN_KEY_CHECKS = 1");
+            }
+        }
+    }
+
+    private void importSequence(String dbName, JsonParser parser, JdbcTemplate jdbcTemplate) throws IOException {
+        if (hasSequence()) {
+            JsonToken fieldNameToken = parser.nextToken();
+            if (fieldNameToken == JsonToken.FIELD_NAME) {
+                String fieldName = parser.getCurrentName();
+                if (fieldName.equalsIgnoreCase(sequenceName())) {
+                    JsonToken current = parser.nextToken();
+                    if (current == JsonToken.VALUE_NUMBER_INT) {
+                        long sequenceValue = parser.getNumberValue().longValue();
+                        if (dbName.equals("MySQL")) {
+                            jdbcTemplate.execute("ALTER TABLE " + getTable() + " AUTO_INCREMENT = " + sequenceValue);
+                        } else if (dbName.equals("H2")) {
+                            jdbcTemplate.execute("ALTER SEQUENCE " + sequenceName() + " RESTART WITH " + sequenceValue);
+                        }
+                    }
+                }
+            }
+            play.Logger.info("imported sequence {{}}", sequenceName());
+        }
+    }
+
+    private void importDataFromArray(JsonParser parser, JdbcTemplate jdbcTemplate, int batchSize) throws IOException {
+        int importedNodesCount = 0;
+        final List<JsonNode> nodes = new ArrayList<>();
+        while (parser.nextToken() != JsonToken.END_ARRAY) {
+            final JsonNode node = parser.readValueAsTree();
+            nodes.add(node);
+            if (nodes.size() == batchSize) {
+                importedNodesCount += batchUpdate(jdbcTemplate, nodes).length;
+                nodes.clear();
+            }
+        }
+        if (nodes.size() > 0) {
+            importedNodesCount += batchUpdate(jdbcTemplate, nodes).length;
+        }
+        play.Logger.info("imported {{}} {}", importedNodesCount, getTable());
+    }
+
+    private void truncateTable(JdbcTemplate jdbcTemplate) {
+        play.Logger.debug("truncate table {}", getTable());
+        jdbcTemplate.execute("TRUNCATE TABLE " + getTable());
+        play.Logger.debug("truncated table {}", getTable());
+    }
+
+    private int[] batchUpdate(JdbcTemplate jdbcTemplate, final List<JsonNode> nodes) {
+        int[] updateCounts = jdbcTemplate.batchUpdate(getInsertSql(), new BatchPreparedStatementSetter() {
+            @Override
+            public void setValues(PreparedStatement ps, int i) throws SQLException {
+                setPreparedStatement(ps, nodes.get(i));
+            }
+
+            @Override
+            public int getBatchSize() {
+                return nodes.size();
+            }
+        });
+        return updateCounts;
+    }
+
+    /**
+     * This method is used when importing data.
+     * Set a preparedStatement with a JsonNode.
+     *
+     * @param ps
+     * @param node
+     * @throws SQLException
+     * @see #importData(JsonParser, JdbcTemplate)
+     */
+    abstract protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException;
+
+    /**
+     * Thia method is used when exporting data.
+     * Set a node with JsonGenerator from a ResultSet.
+     *
+     * @param generator
+     * @param rs
+     * @throws IOException
+     * @throws SQLException
+     * @see #exportData(JsonGenerator, JdbcTemplate)
+     */
+    abstract protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException;
+
+    /**
+     * Insert sql is used when importing data.
+     *
+     * @return insertion sql
+     */
+    abstract protected String getInsertSql();
+
+    /**
+     * Select sql is used when exporting data
+     *
+     * @return selection sql
+     */
+    abstract protected String getSelectSql();
+
+    protected boolean hasSequence() {
+        return true;
+    }
+
+    protected String sequenceName() {
+        return getTable() + "_SEQ";
+    }
+
+}
 
app/data/Exchanger.java (added)
+++ app/data/Exchanger.java
@@ -0,0 +1,75 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.io.IOException;
+
+/**
+ * @author Keeun Baik
+ */
+public interface Exchanger {
+
+    /**
+     * The thable name is used when exporting and importing data.
+     * YOU SHOULD RETURN DATABASE TABLE NAME NOT ENTITY NAME.
+     *
+     * @return table name
+     */
+    String getTable();
+
+    /**
+     * Read data from database with {@code jdbcTemplate}
+     * and write it as json with {@code generator}.
+     *
+     * You should make a json with a field name and an array. like:
+     * {"users": [{"id":1, "loginId":"keesun}, {"id":2, "loginId":"doortts"}]}
+     *
+     * The field name should be a table name and an entity inside the array represents a row.
+     * The one json node inside the json array, has exactly same field name and value with the row.
+     * As a result, YOU SHOULD CHANGE THE IMPLEMENTATIONS IF YOU HAVE CHANGED THE DB SCHEMA OR MAPPING.
+     *
+     * @param dbName
+     * @param generator
+     * @param jdbcTemplate
+     * @throws IOException
+     */
+    void exportData(String dbName, String catalogName, JsonGenerator generator, JdbcTemplate jdbcTemplate) throws IOException;
+
+    /**
+     * Read data from a {@code parser}, and write the data into database with {@code jdbcTemplate}.
+     *
+     * This operation assumes that the sequence of the json data which will be loaded by {@code parser}
+     * is exactly same with exported data with {@link #exportData(String, String, JsonGenerator, JdbcTemplate)}.
+     *
+     * YOU SHOULD BACKUP DATABASE BEFORE USING THIS OPERATION, because this operation usually
+     * truncate existing table and insert all data read from the {@code jdbcTemplate}, in some cases,
+     * you can lose all data or break referential integrity.
+     *
+     * @param parser
+     * @param jdbcTemplate
+     * @throws IOException
+     */
+    void importData(String dbName, JsonParser parser, JdbcTemplate jdbcTemplate) throws IOException;
+}
 
app/data/exchangers/AssigneeDataExchanger.java (added)
+++ app/data/exchangers/AssigneeDataExchanger.java
@@ -0,0 +1,74 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class AssigneeDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id";
+    private static final String USER_ID = "user_id";
+    private static final String PROJECT_ID = "project_id";
+
+    @Override
+    protected boolean hasSequence() {
+        return true;
+    }
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setLong(index++, node.get(USER_ID).longValue());
+        ps.setLong(index++, node.get(PROJECT_ID).longValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putLong(generator, USER_ID, rs, index++);
+        putLong(generator, PROJECT_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ASSIGNEE";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ASSIGNEE (ID, USER_ID, PROJECT_ID) " + values(3);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, USER_ID, PROJECT_ID FROM ASSIGNEE";
+    }
+}
 
app/data/exchangers/AttachmentDataExchanger.java (added)
+++ app/data/exchangers/AttachmentDataExchanger.java
@@ -0,0 +1,91 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class AttachmentDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id";
+    private static final String NAME = "name";
+    private static final String HASH = "hash";
+    private static final String CONTAINER_TYPE = "content_type";
+    private static final String MIME_TYPE = "mime_type";
+    private static final String SIZE = "size";
+    private static final String CONTAINER_ID = "container_id";
+    private static final String CREATED_DATE = "created_date";
+
+    @Override
+    protected boolean hasSequence() {
+        return true;
+    }
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(NAME).textValue());
+        ps.setString(index++, node.get(HASH).textValue());
+        ps.setString(index++, node.get(CONTAINER_TYPE).textValue());
+        ps.setString(index++, node.get(MIME_TYPE).textValue());
+        ps.setLong(index++, node.get(SIZE).longValue());
+        ps.setString(index++, node.get(CONTAINER_ID).textValue());
+        ps.setDate(index++, date(node.get(CREATED_DATE).longValue()));
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, NAME, rs, index++);
+        putString(generator, HASH, rs, index++);
+        putString(generator, CONTAINER_TYPE, rs, index++);
+        putString(generator, MIME_TYPE, rs, index++);
+        putLong(generator, SIZE, rs, index++);
+        putString(generator, CONTAINER_ID, rs, index++);
+        putDate(generator, CREATED_DATE, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ATTACHMENT";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ATTACHMENT (ID, NAME, HASH, CONTAINER_TYPE, MIME_TYPE, SIZE, CONTAINER_ID, " +
+                "CREATED_DATE) " + values(8);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, NAME, HASH, CONTAINER_TYPE, MIME_TYPE, SIZE, CONTAINER_ID, CREATED_DATE " +
+                "FROM ATTACHMENT";
+    }
+}
 
app/data/exchangers/CommentThreadDataExchanger.java (added)
+++ app/data/exchangers/CommentThreadDataExchanger.java
@@ -0,0 +1,119 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class CommentThreadDataExchanger extends DefaultExchanger {
+    private static final String DTYPE = "dtype";
+    private static final String ID = "id";
+    private static final String PROJECT_ID = "project_id";
+    private static final String AUTHOR_ID = "author_id";
+    private static final String AUTHOR_LOGIN_ID = "author_login_id";
+    private static final String AUTHOR_NAME = "author_name";
+    private static final String STATE = "state";
+    private static final String COMMIT_ID = "commit_id";
+    private static final String PATH = "path";
+    private static final String START_SIDE = "start_side";
+    private static final String START_LINE = "start_line";
+    private static final String START_COLUMN = "start_column";
+    private static final String END_SIDE = "end_side";
+    private static final String END_LINE = "end_line";
+    private static final String END_COLUMN = "end_column";
+    private static final String PULL_REQUEST_ID = "pull_request_id";
+    private static final String CREATED_DATE = "created_date";
+    private static final String PREV_COMMIT_ID = "prev_commit_id";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setString(index++, node.get(DTYPE).textValue()); //VARCHAR  nullable? 0
+        ps.setLong(index++, node.get(ID).longValue()); //BIGINT  nullable? 0
+        ps.setLong(index++, node.get(PROJECT_ID).longValue()); //BIGINT  nullable? 0
+        setNullableLong(ps, index++, node, AUTHOR_ID);
+        ps.setString(index++, node.get(AUTHOR_LOGIN_ID).textValue()); //VARCHAR  nullable? 1
+        ps.setString(index++, node.get(AUTHOR_NAME).textValue()); //VARCHAR  nullable? 1
+        ps.setString(index++, node.get(STATE).textValue()); //VARCHAR  nullable? 1
+        ps.setString(index++, node.get(COMMIT_ID).textValue()); //VARCHAR  nullable? 1
+        ps.setString(index++, node.get(PATH).textValue()); //VARCHAR  nullable? 1
+        ps.setString(index++, node.get(START_SIDE).textValue()); //VARCHAR  nullable? 1
+        setNullableLong(ps, index++, node, START_LINE);
+        setNullableLong(ps, index++, node, START_COLUMN);
+        ps.setString(index++, node.get(END_SIDE).textValue()); //VARCHAR  nullable? 1
+        setNullableLong(ps, index++, node, END_LINE);
+        setNullableLong(ps, index++, node, END_COLUMN);
+        setNullableLong(ps, index++, node, PULL_REQUEST_ID);
+        ps.setTimestamp(index++, timestamp(node.get(CREATED_DATE).longValue())); //TIMESTAMP  nullable? 1
+        ps.setString(index++, node.get(PREV_COMMIT_ID).textValue()); //VARCHAR
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putString(generator, DTYPE, rs, index++);
+        putLong(generator, ID, rs, index++);
+        putLong(generator, PROJECT_ID, rs, index++);
+        putLong(generator, AUTHOR_ID, rs, index++);
+        putString(generator, AUTHOR_LOGIN_ID, rs, index++);
+        putString(generator, AUTHOR_NAME, rs, index++);
+        putString(generator, STATE, rs, index++);
+        putString(generator, COMMIT_ID, rs, index++);
+        putString(generator, PATH, rs, index++);
+        putString(generator, START_SIDE, rs, index++);
+        putLong(generator, START_LINE, rs, index++);
+        putLong(generator, START_COLUMN, rs, index++);
+        putString(generator, END_SIDE, rs, index++);
+        putLong(generator, END_LINE, rs, index++);
+        putLong(generator, END_COLUMN, rs, index++);
+        putLong(generator, PULL_REQUEST_ID, rs, index++);
+        putTimestamp(generator, CREATED_DATE, rs, index++);
+        putString(generator, PREV_COMMIT_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "COMMENT_THREAD";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO COMMENT_THREAD (DTYPE, ID, PROJECT_ID, AUTHOR_ID, " +
+                "AUTHOR_LOGIN_ID, AUTHOR_NAME, STATE, COMMIT_ID, PATH, START_SIDE, START_LINE, " +
+                "START_COLUMN, END_SIDE, END_LINE, END_COLUMN, PULL_REQUEST_ID, CREATED_DATE, " +
+                "PREV_COMMIT_ID) " + values(18);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT DTYPE, ID, PROJECT_ID, AUTHOR_ID, AUTHOR_LOGIN_ID, AUTHOR_NAME, " +
+                "STATE, COMMIT_ID, PATH, START_SIDE, START_LINE, START_COLUMN, END_SIDE, " +
+                "END_LINE, END_COLUMN, PULL_REQUEST_ID, CREATED_DATE, PREV_COMMIT_ID " +
+                "FROM COMMENT_THREAD";
+    }
+}
 
app/data/exchangers/CommentThreadUserDataExchanger.java (added)
+++ app/data/exchangers/CommentThreadUserDataExchanger.java
@@ -0,0 +1,70 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class CommentThreadUserDataExchanger extends DefaultExchanger {
+    private static final String COMMENT_THREAD_ID = "comment_thread_id";
+    private static final String N4USER_ID = "n4user_id";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(COMMENT_THREAD_ID).longValue());
+        ps.setLong(index++, node.get(N4USER_ID).longValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, COMMENT_THREAD_ID, rs, index++);
+        putLong(generator, N4USER_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "COMMENT_THREAD_N4USER";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO COMMENT_THREAD_N4USER (COMMENT_THREAD_ID, N4USER_ID) " + values(2);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT COMMENT_THREAD_ID, N4USER_ID FROM COMMENT_THREAD_N4USER";
+    }
+
+    @Override
+    protected boolean hasSequence() {
+        return false;
+    }
+}
 
app/data/exchangers/CommitCommentDataExchanger.java (added)
+++ app/data/exchangers/CommitCommentDataExchanger.java
@@ -0,0 +1,95 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class CommitCommentDataExchanger extends DefaultExchanger {
+    private static final String ID = "id";
+    private static final String PROJECT_ID = "project_id";
+    private static final String COMMIT_ID = "commit_id";
+    private static final String PATH = "path";
+    private static final String LINE = "line";
+    private static final String SIDE = "side";
+    private static final String CREATED_DATE = "created_date";
+    private static final String AUTHOR_ID = "author_id";
+    private static final String AUTHOR_LOGIN_ID = "author_login_id";
+    private static final String AUTHOR_NAME = "author_name";
+    private static final String CONTENTS = "contents";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        setNullableLong(ps, index++, node, PROJECT_ID);
+        ps.setString(index++, node.get(COMMIT_ID).textValue());
+        ps.setString(index++, node.get(PATH).textValue());
+        setNullableLong(ps, index++, node, LINE);
+        ps.setString(index++, node.get(SIDE).textValue());
+        ps.setTimestamp(index++, timestamp(node.get(CREATED_DATE).longValue()));
+        setNullableLong(ps, index++, node, AUTHOR_ID);
+        ps.setString(index++, node.get(AUTHOR_LOGIN_ID).textValue());
+        ps.setString(index++, node.get(AUTHOR_NAME).textValue());
+        setClob(ps, index++, node, CONTENTS);
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putLong(generator, PROJECT_ID, rs, index++);
+        putString(generator, COMMIT_ID, rs, index++);
+        putString(generator, PATH, rs, index++);
+        putLong(generator, LINE, rs, index++);
+        putString(generator, SIDE, rs, index++);
+        putTimestamp(generator, CREATED_DATE, rs, index++);
+        putLong(generator, AUTHOR_ID, rs, index++);
+        putString(generator, AUTHOR_LOGIN_ID, rs, index++);
+        putString(generator, AUTHOR_NAME, rs, index++);
+        putClob(generator, CONTENTS, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "COMMIT_COMMENT";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO COMMIT_COMMENT " +
+                "(ID, PROJECT_ID, COMMIT_ID, PATH, LINE, SIDE, CREATED_DATE, " +
+                "AUTHOR_ID, AUTHOR_LOGIN_ID, AUTHOR_NAME, CONTENTS) " + values(11);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, PROJECT_ID, COMMIT_ID, PATH, LINE, SIDE, CREATED_DATE, " +
+                "AUTHOR_ID, AUTHOR_LOGIN_ID, AUTHOR_NAME, CONTENTS FROM COMMIT_COMMENT";
+    }
+}
 
app/data/exchangers/EmailDataExchanger.java (added)
+++ app/data/exchangers/EmailDataExchanger.java
@@ -0,0 +1,74 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class EmailDataExchanger extends DefaultExchanger {
+    private static final String ID = "id";  //BIGINT  nullable? 0
+    private static final String USER_ID = "user_id";  //BIGINT  nullable? 1
+    private static final String EMAIL = "email";  //VARCHAR  nullable? 1
+    private static final String TOKEN = "token";  //VARCHAR  nullable? 1
+    private static final String VALID = "valid";  //BOOLEAN  nullable? 1
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue()); //BIGINT  nullable? 0
+        ps.setLong(index++, node.get(USER_ID).longValue()); //BIGINT  nullable? 1
+        ps.setString(index++, node.get(EMAIL).textValue()); //VARCHAR  nullable? 1
+        ps.setString(index++, node.get(TOKEN).textValue()); //VARCHAR  nullable? 1
+        ps.setBoolean(index++, node.get(VALID).booleanValue()); //BOOLEAN  nullable? 1
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putLong(generator, USER_ID, rs, index++);
+        putString(generator, EMAIL, rs, index++);
+        putString(generator, TOKEN, rs, index++);
+        putBoolean(generator, VALID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "EMAIL";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO EMAIL (ID, USER_ID, EMAIL, TOKEN, VALID)" + values(5);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "select ID, USER_ID, EMAIL, TOKEN, VALID from EMAIL";
+    }
+}
 
app/data/exchangers/IssueCommentDataExchanger.java (added)
+++ app/data/exchangers/IssueCommentDataExchanger.java
@@ -0,0 +1,84 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class IssueCommentDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id";
+    private static final String CREATED_DATE = "created_date";
+    private static final String AUTHOR_ID = "author_id";
+    private static final String AUTHOR_LOGIN_ID = "author_login_id";
+    private static final String AUTHOR_NAME = "author_name";
+    private static final String ISSUE_ID = "issue_id";
+    private static final String CONTENTS = "contents";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setTimestamp(index++, timestamp(node.get(CREATED_DATE).longValue()));
+        ps.setLong(index++, node.get(AUTHOR_ID).longValue());
+        ps.setString(index++, node.get(AUTHOR_LOGIN_ID).textValue());
+        ps.setString(index++, node.get(AUTHOR_NAME).textValue());
+        ps.setLong(index++, node.get(ISSUE_ID).longValue());
+        setClob(ps, index++, node, CONTENTS);
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putTimestamp(generator, CREATED_DATE, rs, index++);
+        putLong(generator, AUTHOR_ID, rs, index++);
+        putString(generator, AUTHOR_LOGIN_ID, rs, index++);
+        putString(generator, AUTHOR_NAME, rs, index++);
+        putLong(generator, ISSUE_ID, rs, index++);
+        putClob(generator, CONTENTS, rs, index++);
+
+    }
+
+    @Override
+    public String getTable() {
+        return "ISSUE_COMMENT";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ISSUE_COMMENT (ID, CREATED_DATE, AUTHOR_ID, AUTHOR_LOGIN_ID, AUTHOR_NAME, " +
+                "ISSUE_ID, CONTENTS) " + values(7);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, CREATED_DATE, AUTHOR_ID, AUTHOR_LOGIN_ID, AUTHOR_NAME, ISSUE_ID, CONTENTS " +
+                "FROM ISSUE_COMMENT";
+    }
+}
 
app/data/exchangers/IssueCommentVoterDataExchanger.java (added)
+++ app/data/exchangers/IssueCommentVoterDataExchanger.java
@@ -0,0 +1,71 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class IssueCommentVoterDataExchanger extends DefaultExchanger {
+
+    private static final String ISSUE_COMMENT_ID = "issue_comment_id";
+    private static final String USER_ID = "user_id";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ISSUE_COMMENT_ID).longValue());
+        ps.setLong(index++, node.get(USER_ID).longValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ISSUE_COMMENT_ID, rs, index++);
+        putLong(generator, USER_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ISSUE_COMMENT_VOTER";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ISSUE_COMMENT_VOTER (ISSUE_COMMENT_ID, USER_ID) " + values(2);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ISSUE_COMMENT_ID, USER_ID FROM ISSUE_COMMENT_VOTER";
+    }
+
+    @Override
+    protected boolean hasSequence() {
+        return false;
+    }
+}
 
app/data/exchangers/IssueDataExchanger.java (added)
+++ app/data/exchangers/IssueDataExchanger.java
@@ -0,0 +1,108 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class IssueDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id";
+    private static final String TITLE = "title";
+    private static final String BODY = "body";
+    private static final String CREATED_DATE = "created_date";
+    private static final String NUM_OF_COMMENTS = "num_of_comments";
+    private static final String MILESTONE_ID = "milestone_id";
+    private static final String AUTHOR_ID = "author_id";
+    private static final String AUTHOR_LOGIN_ID = "author_login_id";
+    private static final String AUTHOR_NAME = "author_name";
+    private static final String STATE = "state";
+    private static final String PROJECT_ID = "project_id";
+    private static final String ASSIGNEE_ID = "assignee_id";
+    private static final String NUMBER = "number";
+    private static final String UPDATED_DATE = "updated_date";
+    private static final String DUE_DATE = "due_date";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(TITLE).textValue());
+        setClob(ps, index++, node, BODY);
+        ps.setTimestamp(index++, timestamp(node.get(CREATED_DATE).longValue()));
+        ps.setInt(index++, node.get(NUM_OF_COMMENTS).intValue());
+        setNullableLong(ps, index++, node, MILESTONE_ID);
+        ps.setLong(index++, node.get(AUTHOR_ID).longValue());
+        ps.setString(index++, node.get(AUTHOR_LOGIN_ID).textValue());
+        ps.setString(index++, node.get(AUTHOR_NAME).textValue());
+        ps.setInt(index++, node.get(STATE).intValue());
+        ps.setLong(index++, node.get(PROJECT_ID).longValue());
+        setNullableLong(ps, index++, node, ASSIGNEE_ID);
+        ps.setLong(index++, node.get(NUMBER).longValue());
+        ps.setTimestamp(index++, timestamp(node.get(UPDATED_DATE).longValue()));
+        ps.setTimestamp(index++, timestamp(node.get(DUE_DATE).longValue()));
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, TITLE, rs, index++);
+        putClob(generator, BODY, rs, index++);
+        putTimestamp(generator, CREATED_DATE, rs, index++);
+        putInt(generator, NUM_OF_COMMENTS, rs, index++);
+        putLong(generator, MILESTONE_ID, rs, index++);
+        putLong(generator, AUTHOR_ID, rs, index++);
+        putString(generator, AUTHOR_LOGIN_ID, rs, index++);
+        putString(generator, AUTHOR_NAME, rs, index++);
+        putInt(generator, STATE, rs, index++);
+        putLong(generator, PROJECT_ID, rs, index++);
+        putLong(generator, ASSIGNEE_ID, rs, index++);
+        putLong(generator, NUMBER, rs, index++);
+        putTimestamp(generator, UPDATED_DATE, rs, index++);
+        putTimestamp(generator, DUE_DATE, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ISSUE";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ISSUE (ID, TITLE, BODY, CREATED_DATE, NUM_OF_COMMENTS, MILESTONE_ID, AUTHOR_ID, " +
+                "AUTHOR_LOGIN_ID, AUTHOR_NAME, STATE, PROJECT_ID, ASSIGNEE_ID, NUMBER, UPDATED_DATE, DUE_DATE) " +
+                values(15);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, TITLE, BODY, CREATED_DATE, NUM_OF_COMMENTS, MILESTONE_ID, AUTHOR_ID, AUTHOR_LOGIN_ID, " +
+                "AUTHOR_NAME, STATE, PROJECT_ID, ASSIGNEE_ID, NUMBER, UPDATED_DATE, DUE_DATE FROM ISSUE";
+    }
+}
 
app/data/exchangers/IssueEventDataExchanger.java (added)
+++ app/data/exchangers/IssueEventDataExchanger.java
@@ -0,0 +1,86 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class IssueEventDataExchanger extends DefaultExchanger {
+    private static final String ID = "id";
+    private static final String CREATED = "created";
+    private static final String SENDER_LOGIN_ID = "sender_login_id";
+    private static final String ISSUE_ID = "issue_id";
+    private static final String EVENT_TYPE = "event_type";
+    private static final String OLD_VALUE = "old_value";
+    private static final String NEW_VALUE = "new_value";
+    private static final String SENDER_EMAIL = "sender_email";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setTimestamp(index++, timestamp(node.get(CREATED).longValue()));
+        ps.setString(index++, node.get(SENDER_LOGIN_ID).textValue());
+        setNullableLong(ps, index++, node, ISSUE_ID);
+        ps.setString(index++, node.get(EVENT_TYPE).textValue());
+        setClob(ps, index++, node, OLD_VALUE);
+        setClob(ps, index++, node, NEW_VALUE);
+        ps.setString(index++, node.get(SENDER_EMAIL).textValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putTimestamp(generator, CREATED, rs, index++);
+        putString(generator, SENDER_LOGIN_ID, rs, index++);
+        putLong(generator, ISSUE_ID, rs, index++);
+        putString(generator, EVENT_TYPE, rs, index++);
+        putClob(generator, OLD_VALUE, rs, index++);
+        putClob(generator, NEW_VALUE, rs, index++);
+        putString(generator, SENDER_EMAIL, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ISSUE_EVENT";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ISSUE_EVENT " +
+                "(ID, CREATED, SENDER_LOGIN_ID, ISSUE_ID, EVENT_TYPE, OLD_VALUE, NEW_VALUE, SENDER_EMAIL) " +
+                values(8);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, CREATED, SENDER_LOGIN_ID, ISSUE_ID, EVENT_TYPE, OLD_VALUE, " +
+                "NEW_VALUE, SENDER_EMAIL FROM ISSUE_EVENT";
+    }
+}
 
app/data/exchangers/IssueIssueLabelDataExchanger.java (added)
+++ app/data/exchangers/IssueIssueLabelDataExchanger.java
@@ -0,0 +1,71 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class IssueIssueLabelDataExchanger extends DefaultExchanger {
+
+    private static final String ISSUE_ID = "issue_id"; // BIGINT(19) NOT NULL
+    private static final String ISSUE_LABEL_ID = "issue_label_id"; // BIGINT(19) NOT NULL
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ISSUE_ID).longValue());
+        ps.setLong(index++, node.get(ISSUE_LABEL_ID).longValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ISSUE_ID, rs, index++);
+        putLong(generator, ISSUE_LABEL_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ISSUE_ISSUE_LABEL";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ISSUE_ISSUE_LABEL (ISSUE_ID, ISSUE_LABEL_ID) " + values(2);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ISSUE_ID, ISSUE_LABEL_ID FROM ISSUE_ISSUE_LABEL";
+    }
+
+    @Override
+    protected boolean hasSequence() {
+        return false;
+    }
+}
 
app/data/exchangers/IssueLabelCategoryDataExchanger.java (added)
+++ app/data/exchangers/IssueLabelCategoryDataExchanger.java
@@ -0,0 +1,72 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class IssueLabelCategoryDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id";
+    private static final String PROJECT_ID = "project_id";
+    private static final String NAME = "name";
+    private static final String IS_EXCLUSIVE = "is_exclusive";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setLong(index++, node.get(PROJECT_ID).longValue());
+        ps.setString(index++, node.get(NAME).textValue());
+        ps.setBoolean(index++, node.get(IS_EXCLUSIVE).booleanValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putLong(generator, PROJECT_ID, rs, index++);
+        putString(generator, NAME, rs, index++);
+        putBoolean(generator, IS_EXCLUSIVE, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ISSUE_LABEL_CATEGORY";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ISSUE_LABEL_CATEGORY (ID, PROJECT_ID, NAME, IS_EXCLUSIVE) " + values(4);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, PROJECT_ID, NAME, IS_EXCLUSIVE FROM ISSUE_LABEL_CATEGORY";
+    }
+}
 
app/data/exchangers/IssueLabelDataExchanger.java (added)
+++ app/data/exchangers/IssueLabelDataExchanger.java
@@ -0,0 +1,75 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class IssueLabelDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id"; // BIGINT(19) NOT NULL
+    private static final String COLOR = "color"; // VARCHAR(255)
+    private static final String NAME = "name"; // VARCHAR(255)
+    private static final String PROJECT_ID = "project_id"; // BIGINT(19)
+    private static final String CATEGORY_ID = "category_id"; // BIGINT(19) NOT NULL
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(COLOR).textValue());
+        ps.setString(index++, node.get(NAME).textValue());
+        setNullableLong(ps, index++, node, PROJECT_ID);
+        ps.setLong(index++, node.get(CATEGORY_ID).longValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, COLOR, rs, index++);
+        putString(generator, NAME, rs, index++);
+        putLong(generator, PROJECT_ID, rs, index++);
+        putLong(generator, CATEGORY_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ISSUE_LABEL";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ISSUE_LABEL (ID, COLOR, NAME, PROJECT_ID, CATEGORY_ID) " + values(5);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, COLOR, NAME, PROJECT_ID, CATEGORY_ID FROM ISSUE_LABEL";
+    }
+}
 
app/data/exchangers/IssueVoterDataExchanger.java (added)
+++ app/data/exchangers/IssueVoterDataExchanger.java
@@ -0,0 +1,71 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class IssueVoterDataExchanger extends DefaultExchanger {
+
+    private static final String ISSUE_ID = "issue_id"; // BIGINT(19) NOT NULL
+    private static final String USER_ID = "user_id"; // BIGINT(19) NOT NULL
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ISSUE_ID).longValue());
+        ps.setLong(index++, node.get(USER_ID).longValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ISSUE_ID, rs, index++);
+        putLong(generator, USER_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ISSUE_VOTER";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ISSUE_VOTER (ISSUE_ID, USER_ID) " + values(2);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ISSUE_ID, USER_ID FROM ISSUE_VOTER";
+    }
+
+    @Override
+    protected boolean hasSequence() {
+        return false;
+    }
+}
 
app/data/exchangers/LabelDataExchanger.java (added)
+++ app/data/exchangers/LabelDataExchanger.java
@@ -0,0 +1,69 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class LabelDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id";
+    private static final String NAME = "name";
+    private static final String CATEGORY = "category";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(NAME).textValue());
+        ps.setString(index++, node.get(CATEGORY).textValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, NAME, rs, index++);
+        putString(generator, CATEGORY, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "LABEL";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO LABEL (ID, NAME, CATEGORY) " + values(3);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, NAME, CATEGORY FROM LABEL";
+    }
+}
 
app/data/exchangers/MentionDataExchanger.java (added)
+++ app/data/exchangers/MentionDataExchanger.java
@@ -0,0 +1,71 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class MentionDataExchanger extends DefaultExchanger {
+    private static final String ID = "id";
+    private static final String RESOURCE_TYPE = "resource_type";
+    private static final String RESOURCE_ID = "resource_id";
+    private static final String USER_ID = "user_id";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(RESOURCE_TYPE).textValue());
+        ps.setString(index++, node.get(RESOURCE_ID).textValue());
+        setNullableLong(ps, index++, node, USER_ID);
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, RESOURCE_TYPE, rs, index++);
+        putString(generator, RESOURCE_ID, rs, index++);
+        putLong(generator, USER_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "MENTION";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO MENTION (ID, RESOURCE_TYPE, RESOURCE_ID, USER_ID) " + values(4);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, RESOURCE_TYPE, RESOURCE_ID, USER_ID FROM MENTION";
+    }
+}
 
app/data/exchangers/MilestoneDataExchanger.java (added)
+++ app/data/exchangers/MilestoneDataExchanger.java
@@ -0,0 +1,79 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class MilestoneDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id"; // BIGINT(19) NOT NULL
+    private static final String TITLE = "title"; // VARCHAR(255)
+    private static final String DUE_DATE = "due_date"; // TIMESTAMP(23, 10)
+    private static final String CONTENTS = "contents"; // CLOB(2147483647)
+    private static final String STATE = "state"; // INTEGER(10)
+    private static final String PROJECT_ID = "project_id"; // BIGINT(19)
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(TITLE).textValue());
+        ps.setTimestamp(index++, timestamp(node.get(DUE_DATE).longValue()));
+        setClob(ps, index++, node, CONTENTS);
+        ps.setInt(index++, node.get(STATE).intValue());
+        setNullableLong(ps, index++, node, PROJECT_ID);
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, TITLE, rs, index++);
+        putTimestamp(generator, DUE_DATE, rs, index++);
+        putClob(generator, CONTENTS, rs, index++);
+        putInt(generator, STATE, rs, index++);
+        putLong(generator, PROJECT_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "MILESTONE";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO MILESTONE (ID, TITLE, DUE_DATE, CONTENTS, STATE, PROJECT_ID) " +
+                values(6);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, TITLE, DUE_DATE, CONTENTS, STATE, PROJECT_ID FROM MILESTONE";
+    }
+}
 
app/data/exchangers/NotificationEventDataExchanger.java (added)
+++ app/data/exchangers/NotificationEventDataExchanger.java
@@ -0,0 +1,90 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class NotificationEventDataExchanger extends DefaultExchanger {
+    private static final String ID = "id";
+    private static final String TITLE = "title";
+    private static final String SENDER_ID = "sender_id";
+    private static final String CREATED = "created";
+    private static final String RESOURCE_TYPE = "resource_type";
+    private static final String RESOURCE_ID = "resource_id";
+    private static final String EVENT_TYPE = "event_type";
+    private static final String OLD_VALUE = "old_value";
+    private static final String NEW_VALUE = "new_value";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(TITLE).textValue());
+        setNullableLong(ps, index++, node, SENDER_ID);
+        ps.setTimestamp(index++, timestamp(node.get(CREATED).longValue()));
+        ps.setString(index++, node.get(RESOURCE_TYPE).textValue());
+        ps.setString(index++, node.get(RESOURCE_ID).textValue());
+        ps.setString(index++, node.get(EVENT_TYPE).textValue());
+        setClob(ps, index++, node, OLD_VALUE);
+        setClob(ps, index++, node, NEW_VALUE);
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, TITLE, rs, index++);
+        putLong(generator, SENDER_ID, rs, index++);
+        putTimestamp(generator, CREATED, rs, index++);
+        putString(generator, RESOURCE_TYPE, rs, index++);
+        putString(generator, RESOURCE_ID, rs, index++);
+        putString(generator, EVENT_TYPE, rs, index++);
+        putClob(generator, OLD_VALUE, rs, index++);
+        putClob(generator, NEW_VALUE, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "NOTIFICATION_EVENT";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO NOTIFICATION_EVENT " +
+                "(ID, TITLE, SENDER_ID, CREATED, RESOURCE_TYPE, " +
+                "RESOURCE_ID, EVENT_TYPE, OLD_VALUE, NEW_VALUE) " + values(9);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, TITLE, SENDER_ID, CREATED, RESOURCE_TYPE, " +
+                "RESOURCE_ID, EVENT_TYPE, OLD_VALUE, NEW_VALUE " +
+                "FROM NOTIFICATION_EVENT";
+    }
+}
 
app/data/exchangers/NotificationEventUserDataExchanger.java (added)
+++ app/data/exchangers/NotificationEventUserDataExchanger.java
@@ -0,0 +1,70 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class NotificationEventUserDataExchanger extends DefaultExchanger {
+    private static final String NOTIFICATION_EVENT_ID = "notification_event_id";
+    private static final String N4USER_ID = "n4user_id";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(NOTIFICATION_EVENT_ID).longValue());
+        ps.setLong(index++, node.get(N4USER_ID).longValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, NOTIFICATION_EVENT_ID, rs, index++);
+        putLong(generator, N4USER_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "NOTIFICATION_EVENT_N4USER";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO NOTIFICATION_EVENT_N4USER (NOTIFICATION_EVENT_ID, N4USER_ID) " + values(2);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT NOTIFICATION_EVENT_ID, N4USER_ID FROM NOTIFICATION_EVENT_N4USER";
+    }
+
+    @Override
+    protected boolean hasSequence() {
+        return false;
+    }
+}
 
app/data/exchangers/NotificationMailDataExchanger.java (added)
+++ app/data/exchangers/NotificationMailDataExchanger.java
@@ -0,0 +1,65 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class NotificationMailDataExchanger extends DefaultExchanger {
+    private static final String ID = "id";
+    private static final String NOTIFICATION_EVENT_ID = "notification_event_id";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        setNullableLong(ps, index++, node, NOTIFICATION_EVENT_ID);
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putLong(generator, NOTIFICATION_EVENT_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "NOTIFICATION_MAIL";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO NOTIFICATION_MAIL (ID, NOTIFICATION_EVENT_ID) " + values(2);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, NOTIFICATION_EVENT_ID FROM NOTIFICATION_MAIL";
+    }
+}
 
app/data/exchangers/OrganizationDataExchanger.java (added)
+++ app/data/exchangers/OrganizationDataExchanger.java
@@ -0,0 +1,72 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class OrganizationDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id";
+    private static final String NAME = "name";
+    private static final String DESCR = "descr";
+    private static final String CREATED = "created";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(NAME).textValue());
+        ps.setString(index++, node.get(DESCR).textValue());
+        ps.setTimestamp(index++, timestamp(node.get(CREATED).longValue()));
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, NAME, rs, index++);
+        putString(generator, DESCR, rs, index++);
+        putTimestamp(generator, CREATED, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ORGANIZATION";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ORGANIZATION (ID, NAME, DESCR, CREATED) " + values(4);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, NAME, DESCR, CREATED FROM ORGANIZATION";
+    }
+}
 
app/data/exchangers/OrganizationUserDataExchanger.java (added)
+++ app/data/exchangers/OrganizationUserDataExchanger.java
@@ -0,0 +1,72 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Keeun Baik
+ */
+public class OrganizationUserDataExchanger extends DefaultExchanger {
+
+    private static final String ID = "id";
+    private static final String USER_ID = "user_id";
+    private static final String ORGANIZATION_ID = "organization_id";
+    private static final String ROLE_ID = "role_id";
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setLong(index++, node.get(USER_ID).longValue());
+        ps.setLong(index++, node.get(ORGANIZATION_ID).longValue());
+        ps.setLong(index++, node.get(ROLE_ID).longValue());
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putLong(generator, USER_ID, rs, index++);
+        putLong(generator, ORGANIZATION_ID, rs, index++);
+        putLong(generator, ROLE_ID, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "ORGANIZATION_USER";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ORGANIZATION_USER (ID, USER_ID, ORGANIZATION_ID, ROLE_ID) " + values(4);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, USER_ID, ORGANIZATION_ID, ROLE_ID FROM ORGANIZATION_USER";
+    }
+}
 
app/data/exchangers/OriginalEmailDataExchanger.java (added)
+++ app/data/exchangers/OriginalEmailDataExchanger.java
@@ -0,0 +1,75 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Yi EungJun
+ */
+public class OriginalEmailDataExchanger extends DefaultExchanger {
+    private static final String ID = "id";
+    private static final String MESSAGE_ID = "message_id";
+    private static final String RESOURCE_TYPE = "resource_type";
+    private static final String RESOURCE_ID = "resource_id";
+    private static final String HANDLED_DATE = "handled_date";
+
+    @Override
+    public String getTable() {
+        return "ORIGINAL_EMAIL";
+    }
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue());
+        ps.setString(index++, node.get(MESSAGE_ID).textValue());
+        ps.setString(index++, node.get(RESOURCE_TYPE).textValue());
+        ps.setString(index++, node.get(RESOURCE_ID).textValue());
+        ps.setTimestamp(index++, timestamp(node.get(HANDLED_DATE).longValue()));
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putString(generator, MESSAGE_ID, rs, index++);
+        putString(generator, RESOURCE_TYPE, rs, index++);
+        putString(generator, RESOURCE_ID, rs, index++);
+        putTimestamp(generator, HANDLED_DATE, rs, index++);
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO ORIGINAL_EMAIL " +
+                "(ID, MESSAGE_ID, RESOURCE_TYPE, RESOURCE_ID, HANDLED_DATE) " + values(5);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, MESSAGE_ID, RESOURCE_TYPE, RESOURCE_ID, HANDLED_DATE FROM ORIGINAL_EMAIL";
+    }
+}
 
app/data/exchangers/PostingCommentDataExchanger.java (added)
+++ app/data/exchangers/PostingCommentDataExchanger.java
@@ -0,0 +1,82 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2015 NAVER Corp.
+ * http://yobi.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package data.exchangers;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import data.DefaultExchanger;
+
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * @author Suwon Chae
+ */
+public class PostingCommentDataExchanger extends DefaultExchanger {
+    private static final String ID = "id";  //BIGINT  nullable? 0
+    private static final String CREATED_DATE = "created_date";  //TIMESTAMP  nullable? 1
+    private static final String AUTHOR_ID = "author_id";  //BIGINT  nullable? 1
+    private static final String AUTHOR_LOGIN_ID = "author_login_id";  //VARCHAR  nullable? 1
+    private static final String AUTHOR_NAME = "author_name";  //VARCHAR  nullable? 1
+    private static final String POSTING_ID = "posting_id";  //BIGINT  nullable? 1
+    private static final String CONTENTS = "contents";  //CLOB  nullable? 1
+
+    @Override
+    protected void setPreparedStatement(PreparedStatement ps, JsonNode node) throws SQLException {
+        short index = 1;
+        ps.setLong(index++, node.get(ID).longValue()); //BIGINT  nullable? 0
+        ps.setTimestamp(index++, timestamp(node.get(CREATED_DATE).longValue())); //TIMESTAMP  nullable? 1
+        setNullableLong(ps, index++, node, AUTHOR_ID); //BIGINT  nullable? 1
+        ps.setString(index++, node.get(AUTHOR_LOGIN_ID).textValue()); //VARCHAR  nullable? 1
+        ps.setString(index++, node.get(AUTHOR_NAME).textValue()); //VARCHAR  nullable? 1
+        setNullableLong(ps, index++, node, POSTING_ID); //BIGINT  nullable? 1
+        setClob(ps, index++, node, CONTENTS); //CLOB  nullable? 1
+    }
+
+    @Override
+    protected void setNode(JsonGenerator generator, ResultSet rs) throws IOException, SQLException {
+        short index = 1;
+        putLong(generator, ID, rs, index++);
+        putTimestamp(generator, CREATED_DATE, rs, index++);
+        putLong(generator, AUTHOR_ID, rs, index++);
+        putString(generator, AUTHOR_LOGIN_ID, rs, index++);
+        putString(generator, AUTHOR_NAME, rs, index++);
+        putLong(generator, POSTING_ID, rs, index++);
+        putClob(generator, CONTENTS, rs, index++);
+    }
+
+    @Override
+    public String getTable() {
+        return "POSTING_COMMENT";
+    }
+
+    @Override
+    protected String getInsertSql() {
+        return "INSERT INTO POSTING_COMMENT (ID, CREATED_DATE, AUTHOR_ID, AUTHOR_LOGIN_ID, AUTHOR_NAME, " +
+                "POSTING_ID, CONTENTS) " + values(7);
+    }
+
+    @Override
+    protected String getSelectSql() {
+        return "SELECT ID, CREATED_DATE, AUTHOR_ID, AUTHOR_LOGIN_ID, AUTHOR_NAME, POSTING_ID, CONTENTS " +
+                "FROM POSTING_COMMENT";
+    }
+}
 
app/data/exchangers/PostingDataExchanger.java (added)
+++ app/data/exchangers/PostingDataExchanger.java