说明:这篇文章不探讨 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 方法就能保存到数据库了。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于