1. 概述

Apache Cayenne 是一个开源库,采用 Apache 许可证分发。它提供了建模工具、对象关系映射(ORM)用于本地持久化操作,以及远程服务等功能。

在接下来的章节中,我们将演示如何使用 Apache Cayenne ORM 与 MySQL 数据库交互。

2. Maven 依赖

首先,只需添加以下依赖即可引入 Apache Cayenne 和 MySQL 连接器(JDBC 驱动),用于访问我们的 intro_cayenne 数据库:

<dependency>
    <groupId>org.apache.cayenne</groupId>
    <artifactId>cayenne-server</artifactId>
    <version>4.0.M5</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.44</version>
    <scope>runtime</scope>
</dependency>

接下来配置 Cayenne 建模插件,它用于设计或设置映射文件(作为数据库模式和 Java 对象之间的桥梁):

<plugin>
    <groupId>org.apache.cayenne.plugins</groupId>
    <artifactId>maven-cayenne-modeler-plugin</artifactId>
    <version>4.0.M5</version>
</plugin>

⚠️ 手动编写 XML 映射文件(极少见)不推荐,建议使用 Cayenne 自带的建模工具——这是一个相当高级的工具。

它可根据操作系统从归档文件下载,或直接使用上面 Maven 插件包含的跨平台版本(JAR)。

Maven 中央仓库托管了最新版本的 Apache Cayenne建模插件MySQL Connector

执行 mvn install 构建项目后,通过命令 mvn cayenne-modeler:run 启动建模 GUI,将显示如下界面:

Screen1

3. 配置

要让 Apache Cayenne 正确定位本地数据库,只需在 resources 目录下的 cayenne-project.xml 文件中配置正确的驱动、URL 和用户信息:

<?xml version="1.0" encoding="utf-8"?>
<domain project-version="9">
    <node name="datanode"
          factory="org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory"
          schema-update-strategy="org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy">
        <data-source>
            <driver value="com.mysql.jdbc.Driver"/>
            <url value="jdbc:mysql://localhost:3306/intro_cayenne;create=true"/>
            <connectionPool min="1" max="1"/>
            <login userName="root" password="root"/>
        </data-source>
    </node>
</domain>

关键配置说明:

  • 本地数据库名为 intro_cayenne
  • 若数据库不存在,Cayenne 会自动创建
  • 使用用户名 root 和密码 root(根据你的数据库系统配置修改)

内部由 XMLPoolingDataSourceFactory 负责从与 DataNodeDescriptor 关联的 XML 资源加载 JDBC 连接信息。

⚠️ 这些参数与数据库管理系统和 JDBC 驱动相关,因为该库支持多种数据库。每种数据库都有对应的适配器,详见支持列表

4. 映射与数据库设计

4.1 建模

点击 *"Open Project"*,导航到项目的 resources 文件夹并选择 cayenne-project.xml,建模器将显示如下:

Capture d’écran

此时有两种选择:从现有数据库创建映射结构手动操作。本文将演示使用建模器和现有数据库的方式,帮助快速上手 Cayenne。

查看我们的 intro_cayenne 数据库,它包含两个具有一对多关系的表(一个作者可发表多篇文章):

  • author: id (主键) 和 name
  • article: id (主键), title, content 和 author_id (外键)

进入 *"Tools > Reengineer Database Schema"*,所有映射配置将自动填充。在提示界面填写 cayenne-project.xml 中的数据源配置并点击继续:

Capture d’écran

在下一界面勾选 *"Use Java primitive types"*:

Capture d’écran

确保设置 Java 包为 com.baeldung.apachecayenne.persistent 并保存。XML 配置文件的 defaultPackage 属性将自动更新:

Capture d’écran

在每个 ObjEntity 中指定子类的包名(如下图所示),再次点击 "save" 图标:

Screen5

进入 "Tools > Generate Classes" 菜单,选择 "Standard Persistent Objects" 类型;在 "Classes" 选项卡勾选所有类并点击 *"generate"*。

回到源码目录,将看到生成的持久化类 _Article.java_Author.java

✅ 所有配置保存在 resources 文件夹下的 datamap.map.xml 文件中。

4.2 映射结构

生成的 XML 映射文件使用了 Apache Cayenne 特有的标签:

标签 说明
DataNode(<node>) 数据库模型,包含连接所需信息(数据库名、驱动、凭据)
DataMap(<data-map>) 持久化实体及其关系的容器
DbAttribute(<db-attribute>) 数据库表的列
DbEntity(<db-entity>) 单个数据库表或视图的模型,可包含 DbAttributes 和关系
ObjEntity(<obj-entity>) 单个持久化 Java 类的模型,由 ObjAttributes(实体属性)和 ObjRelationships(其他实体类型的属性)组成
Embeddable(<embeddable>) 作为 ObjEntity 属性的 Java 类模型,对应数据库多列
Procedure(<procedure>) 注册数据库存储过程
Query(<query>) 查询模型,用于在配置文件中映射查询(也可在代码中实现)

完整细节见官方文档

5. Cayenne API

最后一步是使用 Cayenne API 和生成的类进行数据库操作。子类化持久化类是最佳实践,便于后续模型定制。

5.1 创建对象

保存一个 Author 对象,并验证数据库中仅有一条记录:

@Test
public void whenInsert_thenWeGetOneRecordInTheDatabase() {
    Author author = context.newObject(Author.class);
    author.setName("Paul");

    context.commitChanges();

    long records = ObjectSelect.dataRowQuery(Author.class)
      .selectCount(context);
 
    assertEquals(1, records);
}

5.2 读取对象

保存 Author 后,通过特定属性查询获取:

@Test
public void whenInsert_andQueryByFirstName_thenWeGetTheAuthor() {
    Author author = context.newObject(Author.class);
    author.setName("Paul");

    context.commitChanges();

    Author expectedAuthor = ObjectSelect.query(Author.class)
      .where(Author.NAME.eq("Paul"))
      .selectOne(context);

    assertEquals("Paul", expectedAuthor.getName());
}

5.3 获取类的所有记录

保存两个作者后,验证仅能查询到这两条记录:

@Test
public void whenInsert_andQueryAll_thenWeGetTwoAuthors() {
    Author firstAuthor = context.newObject(Author.class);
    firstAuthor.setName("Paul");

    Author secondAuthor = context.newObject(Author.class);
    secondAuthor.setName("Ludovic");

    context.commitChanges();

    List<Author> authors = ObjectSelect
      .query(Author.class)
      .select(context);
 
    assertEquals(2, authors.size());
}

5.4 更新对象

更新操作也很简单,先获取对象再修改属性并提交:

@Test
public void whenUpdating_thenWeGetAnUpatedeAuthor() {
    Author author = context.newObject(Author.class);
    author.setName("Paul");
    context.commitChanges();

    Author expectedAuthor = ObjectSelect.query(Author.class)
      .where(Author.NAME.eq("Paul"))
      .selectOne(context);
    expectedAuthor.setName("Garcia");
    context.commitChanges();

    assertEquals(author.getName(), expectedAuthor.getName());
}

5.5 关联对象

将文章分配给作者:

@Test
public void whenAttachingToArticle_thenTheRelationIsMade() {
    Author author = context.newObject(Author.class);
    author.setName("Paul");

    Article article = context.newObject(Article.class);
    article.setTitle("My post title");
    article.setContent("The content");
    article.setAuthor(author);

    context.commitChanges();

    Author expectedAuthor = ObjectSelect.query(Author.class)
      .where(Author.NAME.eq("Smith"))
      .selectOne(context);

    Article expectedArticle = (expectedAuthor.getArticles()).get(0);
 
    assertEquals(article.getTitle(), expectedArticle.getTitle());
}

5.6 删除对象

删除操作会从数据库中彻底移除记录,后续查询将返回 null

@Test
public void whenDeleting_thenWeLostHisDetails() {
    Author author = context.newObject(Author.class);
    author.setName("Paul");
    context.commitChanges();

    Author savedAuthor = ObjectSelect.query(Author.class)
      .where(Author.NAME.eq("Paul"))
      .selectOne(context);
    if(savedAuthor != null) {
        context.deleteObjects(author);
        context.commitChanges();
    }

    Author expectedAuthor = ObjectSelect.query(Author.class)
      .where(Author.NAME.eq("Paul"))
      .selectOne(context);
 
    assertNull(expectedAuthor);
}

5.7 删除类的所有记录

使用 SQLTemplate 清空表数据(在测试后执行,确保每次测试前数据库为空):

@After
public void deleteAllRecords() {
    SQLTemplate deleteArticles = new SQLTemplate(
      Article.class, "delete from article");
    SQLTemplate deleteAuthors = new SQLTemplate(
      Author.class, "delete from author");

    context.performGenericQuery(deleteArticles);
    context.performGenericQuery(deleteAuthors);
}

6. 总结

本教程演示了如何使用 Apache Cayenne ORM 轻松实现一对多关系的 CRUD 操作。

本文源代码可在 GitHub 获取。


原始标题:Introduction to Apache Cayenne ORM

« 上一篇: rxjava-jdbc 介绍