说明:这篇文章不探讨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 { ClassrepositoryClass; final Latkes.RuntimeDatabase runtimeDatabase = Latkes.getRuntimeDatabase(); switch (runtimeDatabase) { case MYSQL: case H2: case MSSQL: case ORACLE: repositoryClass = (Class ) Class.forName("org.b3log.latke.repository.jdbc.JdbcRepository"); break; case NONE: repositoryClass = (Class ) Class.forName("org.b3log.latke.repository.NoneRepository"); break; default: throw new RuntimeException("The runtime database [" + runtimeDatabase + "] is not support NOW!"); } final Constructor 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)方法
@Overridepublic 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
从方法的命名应该可以很轻易的判断出是什么意思了,就是第一步构造SQL语句,第二步执行SQL语句
private String buildAddSql(final JSONObject jsonObject, final List
判断传进来的JSON中设置的默认主键是否为空,如果为空就设置一个,如果不为空就取出来用来返回,solo项目中的主键名称都是oId,值是时间戳,
调用setProperties(jsonObject, paramlist, sql);方法。
private void setProperties(final JSONObject jsonObject, final List
这段代码的意思就是在根据传进来的JSON拼接SQL语句,最后一句中有一个getName()语句,其实拼接在这里就是对应数据库的表名称,什么时候传进来的,其实是在上面的ArticleRepositoryImpl类中调用父类的无参构造方法时传进来的,在父类中通过反射实例化JdbcRepository对象需要传入name属性,在类JdbcRepository中会经常用到,可以说只要跟数据库相关的操作可能都要用到表名称。
拼接好SQL语句后,就是执行SQL了,至此一个新增操作的全部就解读完毕了。
总结
通过看源码,就不难明白,为什么他说不需要像Hibernate、MyBatis那类ORM框架非得从JSON转到实体类对象,再跟关系型数据库对应了,为什么能前端拼接的JSON能直接调用add方法就能保存到数据库了。