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方法就能儲存到資料庫了。