說明:這篇文章不探讨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方法就能儲存到資料庫了。