天天看點

Latke持久層-新增add方法解讀add源碼追溯總結

Latke持久層-新增add方法解讀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方法就能儲存到資料庫了。