Latke 持久层 - 新增 add 方法解读

本贴最后更新于 2028 天前,其中的信息可能已经时移世改

说明:这篇文章不探讨 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 方法就能保存到数据库了。

  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    70 引用 • 532 回帖 • 706 关注
  • 代码
    459 引用 • 591 回帖 • 8 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • Alexs

    都是拼个 sql,通过对象执行,人家处理的好一点。

    1 回复
  • nobt
    作者

    我觉得处理的好点的原因以及敢自己造轮子的原因是因为把市面上的框架都深入了解了,ORM 都是封装的 JDBC

nobt
保持正道,学好技能,努力工作,升职加薪,等待机会

推荐标签 标签

  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    16 引用 • 68 回帖 • 1 关注
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    17 引用 • 236 回帖 • 434 关注
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 171 关注
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 506 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    139 引用 • 441 回帖
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    207 引用 • 2031 回帖
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    529 引用 • 3527 回帖 • 2 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 592 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    261 引用 • 662 回帖 • 2 关注
  • BND

    BND(Baidu Netdisk Downloader)是一款图形界面的百度网盘不限速下载器,支持 Windows、Linux 和 Mac,详细介绍请看这里

    107 引用 • 1281 回帖 • 22 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    85 引用 • 1192 回帖 • 461 关注
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 41 关注
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    170 引用 • 414 回帖 • 430 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 20 关注
  • 博客

    记录并分享人生的经历。

    270 引用 • 2386 回帖
  • Vim

    Vim 是类 UNIX 系统文本编辑器 Vi 的加强版本,加入了更多特性来帮助编辑源代码。Vim 的部分增强功能包括文件比较(vimdiff)、语法高亮、全面的帮助系统、本地脚本(Vimscript)和便于选择的可视化模式。

    27 引用 • 66 回帖 • 1 关注
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 3 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 408 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    76 引用 • 421 回帖
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 151 关注
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 56 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖
  • flomo

    flomo 是新一代 「卡片笔记」 ,专注在碎片化时代,促进你的记录,帮你积累更多知识资产。

    3 引用 • 74 回帖 • 2 关注
  • V2Ray
    1 引用 • 15 回帖
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    28 引用 • 108 回帖 • 2 关注
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    51 引用 • 190 回帖 • 1 关注