1. 程式人生 > >Latke持久層-新增add方法解讀

Latke持久層-新增add方法解讀

說明:這篇文章不探討Latke框架的IOC/DI部分,Latke框架中的IOC/DI功能跟Spring是很相似的,用起來會覺得很好上手,在這裡只是說明為什麼Latke可以將一個JSON用類似ORM的功能儲存到關係型資料庫。

add原始碼追溯

以solo新增一篇部落格的後臺全過程為例

  • 部落格新增入口
@RequestProcessing(value = "/console/article/", method = HTTPRequestMethod.POST)
    public void addArticle(final HttpServletRequest request, final HttpServletResponse response, final HTTPRequestContext context,
                           final JSONObject requestJSONObject) throws Exception {
       //省略程式碼
       final String articleId = articleMgmtService.addArticle(requestJSONObject);
       //省略程式碼
    }
  • 上面介面呼叫的下一層方法
public String addArticleInternal(final JSONObject article) throws ServiceException {
         //省略程式碼,將前臺傳過來的json封裝成最終新增所需要的JSON資料,呼叫新增方法
	articleRepository.add(article);
	//省略程式碼

    }

articleRepository是通過Latke框架的註解@Inject注入進來的,在它的實現類ArticleRepositoryImpl中沒有add(JSONObject jsonObject)型別的方法,那麼它調的肯定是其父類的add(JSONObject jsonObject)方法

  • 看看ArticleRepository的實現類ArticleRepositoryImpl的結構
public class ArticleRepositoryImpl extends AbstractRepository implements ArticleRepository {
    public ArticleRepositoryImpl() {
        super(Article.ARTICLE);
    }
}
  • 這個無參的構造方法,發現它沒幹什麼事,只是呼叫了父類的無參構造方法,注意Article.ARTICLE後面會用到
public AbstractRepository(final String name) {
        try {
            Class<Repository> repositoryClass;

            final Latkes.RuntimeDatabase runtimeDatabase = Latkes.getRuntimeDatabase();
            switch (runtimeDatabase) {
                case MYSQL:
                case H2:
                case MSSQL:
                case ORACLE:
                    repositoryClass = (Class<Repository>) Class.forName("org.b3log.latke.repository.jdbc.JdbcRepository");

                    break;
                case NONE:
                    repositoryClass = (Class<Repository>) Class.forName("org.b3log.latke.repository.NoneRepository");

                    break;
                default:
                    throw new RuntimeException("The runtime database [" + runtimeDatabase + "] is not support NOW!");
            }

            final Constructor<Repository> constructor = repositoryClass.getConstructor(String.class);

            repository = constructor.newInstance(name);
        } catch (final Exception e) {
            throw new RuntimeException("Can not initialize repository!", e);
        }

        Repositories.addRepository(repository);
        LOGGER.log(Level.INFO, "Constructed repository [name={0}]", name);
    }

Latkes.getRuntimeDatabase()是從配置檔案local.properties中獲取變數名為runtimeDatabase(執行資料庫型別)的值,從程式碼中可以看到,支援三種資料庫:MYSQL、H2、ORACLE,根據runtimeDatabase的值通過反射例項化一個JdbcRepository物件,這個物件就是比較底層的一個持久方法了。

  • 接著再看看ArticleRepositoryImpl的父類AbstractRepository中的add(JSONObject jsonObject)方法
@Override
public String add(final JSONObject jsonObject) throws RepositoryException {
        if (!isWritable() && !isInternalCall()) {
            throw new RepositoryException("The repository [name=" + getName() + "] is not writable at present");
        }

        Repositories.check(getName(), jsonObject, Keys.OBJECT_ID);

        return repository.add(jsonObject);
    }

前面說了repository物件是根據runtimeDatabase的值通過反射例項化一個JdbcRepository物件,那麼呼叫的就是JdbcRepository類中的add方法

@Override
    public String add(final JSONObject jsonObject) throws RepositoryException {
        final JdbcTransaction currentTransaction = TX.get();
        if (null == currentTransaction) {
            throw new RepositoryException("Invoking add() outside a transaction");
        }

        final Connection connection = getConnection();
        final List<Object> paramList = new ArrayList<>();
        final StringBuilder sql = new StringBuilder();
        String ret;

        try {
            if (Latkes.RuntimeDatabase.ORACLE == Latkes.getRuntimeDatabase()) {
                toOracleClobEmpty(jsonObject);
            }
            ret = buildAddSql(jsonObject, paramList, sql);
            JdbcUtil.executeSql(sql.toString(), paramList, connection, false);
            JdbcUtil.fromOracleClobEmpty(jsonObject);
        } catch (final Exception e) {
            LOGGER.log(Level.ERROR, "Add failed", e);

            throw new RepositoryException(e);
        }

        return ret;
    }

從方法的命名應該可以很輕易的判斷出是什麼意思了,就是第一步構造SQL語句,第二步執行SQL語句

private String buildAddSql(final JSONObject jsonObject, final List<Object> paramlist, final StringBuilder sql) throws Exception {
        String ret = null;
        if (!jsonObject.has(Keys.OBJECT_ID)) {
            if (!(KEY_GEN instanceof DBKeyGenerator)) {
                ret = (String) KEY_GEN.gen();
                jsonObject.put(Keys.OBJECT_ID, ret);
            }
        } else {
            ret = jsonObject.getString(Keys.OBJECT_ID);
        }
        setProperties(jsonObject, paramlist, sql);
        return ret;
    }

判斷傳進來的JSON中設定的預設主鍵是否為空,如果為空就設定一個,如果不為空就取出來用來返回,solo專案中的主鍵名稱都是oId,值是時間戳, 呼叫setProperties(jsonObject, paramlist, sql);方法。

private void setProperties(final JSONObject jsonObject, final List<Object> paramlist, final StringBuilder sql) throws Exception {
        final Iterator<String> keys = jsonObject.keys();

        final StringBuilder insertString = new StringBuilder();
        final StringBuilder wildcardString = new StringBuilder();

        boolean isFirst = true;
        String key;
        Object value;

        while (keys.hasNext()) {
            key = keys.next();

            if (isFirst) {
                insertString.append("(").append(key);
                wildcardString.append("(?");
                isFirst = false;
            } else {
                insertString.append(",").append(key);
                wildcardString.append(",?");
            }

            value = jsonObject.get(key);
            paramlist.add(value);

            if (!keys.hasNext()) {
                insertString.append(")");
                wildcardString.append(")");
            }
        }

        sql.append("insert into ").append(getName()).append(insertString).append(" values ").append(wildcardString);
    }

這段程式碼的意思就是在根據傳進來的JSON拼接SQL語句,最後一句中有一個getName()語句,其實拼接在這裡就是對應資料庫的表名稱,什麼時候傳進來的,其實是在上面的ArticleRepositoryImpl類中呼叫父類的無參構造方法時傳進來的,在父類中通過反射例項化JdbcRepository物件需要傳入name屬性,在類JdbcRepository中會經常用到,可以說只要跟資料庫相關的操作可能都要用到表名稱。 拼接好SQL語句後,就是執行SQL了,至此一個新增操作的全部就解讀完畢了。

總結

通過看原始碼,就不難明白,為什麼他說不需要像Hibernate、MyBatis那類ORM框架非得從JSON轉到實體類物件,再跟關係型資料庫對應了,為什麼能前端拼接的JSON能直接呼叫add方法就能儲存到資料庫了。