具有一对多和多对多关系的 JOOQ pojos

Posted

技术标签:

【中文标题】具有一对多和多对多关系的 JOOQ pojos【英文标题】:JOOQ pojos with one-to-many and many-to-many relations 【发布时间】:2014-06-13 07:02:36 【问题描述】:

我很难理解如何处理与 JOOQ 的一对多和多对多关系的 pojo。

我存储玩家创建的位置(一对多关系)。一个位置可以容纳多个可能访问它的其他玩家(多对多)。数据库布局归结为以下内容:

CREATE TABLE IF NOT EXISTS `Player` (
  `player-id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `player` BINARY(16) NOT NULL,
  PRIMARY KEY (`player-id`),
  UNIQUE INDEX `U_player` (`player` ASC))
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS `Location` (
  `location-id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(32) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL,
  `player-id` INT UNSIGNED NOT NULL COMMENT '
  UNIQUE INDEX `U_name` (`name` ASC),
  PRIMARY KEY (`location-id`),
  INDEX `Location_Player_fk` (`player-id` ASC),
  CONSTRAINT `fk_location_players1`
    FOREIGN KEY (`player-id`)
    REFERENCES `Player` (`player-id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS `location2player` (
  `location-id` INT UNSIGNED NOT NULL,
  `player-id` INT UNSIGNED NOT NULL,
  INDEX `fk_ location2player_Location1_idx` (`location-id` ASC),
  INDEX `fk_location2player_Player1_idx` (`player-id` ASC),
  CONSTRAINT `fk_location2player_Location1`
    FOREIGN KEY (`location-id`)
    REFERENCES `Location` (`location-id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_location2player_Player1`
    FOREIGN KEY (`player-id`)
    REFERENCES `Player` (`player-id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

在我的 java 应用程序中,所有这些信息都存储在一个 pojo 中。请注意,玩家和受邀玩家列表可以在应用程序中更新,也需要在数据库中更新:

public class Location 

    private final String name;
    private UUID player;
    private List<UUID> invitedPlayers;

    public void setPlayer(UUID player) 
        this.player = player;
    

    public void invitePlayer(UUID player) 
        invitedPlayers.add(player);
    

    public void uninvitePlayer(UUID player) 
        invitedPlayers.remove(player);
    

    //additional methods…

我可以使用 JOOQ 的 pojo 映射将这三个记录映射到单个 pojo 中吗?我可以从这个 pojo 中使用 JOOQ 的 CRUD 功能来更新一对多和多对多的关系吗?如果不能使用 pojo 映射,除了使用它来编写我的 SQL 语句之外,我可以以任何方式利用 JOOQ 吗?

【问题讨论】:

【参考方案1】:

MULTISET 用于jOOQ 3.15 的嵌套集合

从 jOOQ 3.15 开始,您可以使用standard SQL MULTISET operator 来嵌套集合,并抽象以下 SQL/XML 或 SQL/JSON 序列化格式。您的查询将如下所示:

List<Location> locations
ctx.select(
      LOCATION.NAME,
      LOCATION.PLAYER,
      multiset(
        select(LOCATION2PLAYER.PLAYER_ID)
        .from(LOCATION2PLAYER)
        .where(LOCATION2PLAYER.LOCATION_ID.eq(LOCATION.LOCATION_ID))
      ).as("invitedPlayers")
    )
   .from(LOCATION)
   .fetchInto(Location.class);

如果您的 DTO 是不可变的(例如 Java 16 记录),您甚至可以避免使用反射进行映射,并使用构造函数引用和 new jOOQ 3.15 ad-hoc conversion feature 将类型安全地映射到 DTO 构造函数中。

List<Location> locations
ctx.select(
      LOCATION.NAME,
      LOCATION.PLAYER,
      multiset(
        select(LOCATION2PLAYER.PLAYER_ID)
        .from(LOCATION2PLAYER)
        .where(LOCATION2PLAYER.LOCATION_ID.eq(LOCATION.LOCATION_ID))
      ).as("invitedPlayers").convertFrom(r -> r.map(Record1::value1))
    )
   .from(LOCATION)
   .fetch(Records.mapping(Location::new));

See also this blog post for more details about MULTISET

将 SQL/XML 或 SQL/JSON 用于 jOOQ 3.14 的嵌套集合

从 jOOQ 3.14 开始,如果您的 RDBMS 支持,可以使用 SQL/XML 或 SQL/JSON 嵌套集合。然后,您可以使用 Jackson、Gson 或 JAXB 从文本格式映射回您的 Java 类。例如:

List<Location> locations
ctx.select(
      LOCATION.NAME,
      LOCATION.PLAYER,
      field(
        select(jsonArrayAgg(LOCATION2PLAYER.PLAYER_ID))
        .from(LOCATION2PLAYER)
        .where(LOCATION2PLAYER.LOCATION_ID.eq(LOCATION.LOCATION_ID))
      ).as("invitedPlayers")
        .convertFrom(r -> r.map(Records.mapping(Pla)
    )
   .from(LOCATION)
   .fetch(Records.mapping(Location::new));

在某些数据库产品中,例如 PostgreSQL,您甚至可以使用ARRAY_AGG() 来使用 SQL 数组类型,并跳过使用中间 XML 或 JSON 格式。

请注意,JSON_ARRAYAGG() 将空集聚合到 NULL,而不是空集 []。 If that's a problem, use COALESCE()

历史答案(jOOQ 3.14 之前)

jOOQ 还没有开箱即用地进行这种 POJO 映射,但您可以利用 ModelMapper 之类的东西,它具有专用的 jOOQ integration,在一定程度上适用于这些场景。

本质上,ModelMapper 挂钩到 jOOQ 的 RecordMapper API。更多细节在这里:

http://www.jooq.org/doc/latest/manual/sql-execution/fetching/recordmapper/ http://www.jooq.org/doc/latest/manual/sql-execution/fetching/pojos-with-recordmapper-provider/

【讨论】:

谢谢。但是我如何将多对多数据提取到 JOOQ 的逻辑中呢?在 JDBC 中,我将使用 JOIN 子句获取数据并将每个附加行添加到 pojo 中的集合中,但是使用 JOOQ,RecordMapper 无法访问以前的对象。在创建相应对象时,我是否希望在RecordMapper 中对多对多关系执行另一个查询?还是有其他方法可以做到这一点? 是的,RecordMapper 是一个函数式接口,因此将“有状态”的东西作为分组算法来实现并不容易。但我们也有各种Result.intoGroups()。也许,这些会有帮助?另一种选择是利用像ModelMapper 这样的东西,它本机支持jOOQ。在任何情况下,您都不应该仅仅因为 ResultSet 到 Object 的映射算法变得有点复杂而执行两个查询。 在其他answers(以及您的blog)中,您提到Java 8 的流对于将数据库中的复杂关系正确匹配到POJO 中很有用。从这个角度来说,Streams可以用来解决这个问题吗? @thee:是的,他们当然可以。甚至比使用 Streams 更容易,您可以使用 jOOλ。我在这里的回答并不详尽。我会在不久的将来添加更多示例,感谢您指出这一点。 @thee:您是否有兴趣在用户组中更详细地说明您的用例? groups.google.com/forum/#!forum/jooq-user。如果我知道您的具体期望,也许我可以比这里更好地回答您的问题。当然,您可以使用我提到的工具进行这些映射,但很难说(并在 Stack Overflow 上讨论),这对您来说是否足够好。【参考方案2】:

您可以使用SimpleFlatMapper 查询的结果集。

创建一个以玩家为键的映射器

JdbcMapper<Location> jdbcMapper = 
    JdbcMapperFactory.addKeys("player").newMapper(Location.class);

然后使用 fetchResultSet 获取 ResultSet 并将其传递给映射器。 请注意,orderBy(LOCATION.PLAYER_ID) 很重要,否则您可能会得到拆分位置。

try (ResultSet rs = 
    dsl
        .select(
                LOCATION.NAME.as("name"), 
                LOCATION.PLAYER_ID.as("player"), 
                LOCATION2PLAYER.PLAYERID.as("invited_players_player"))
        .from(LOCATION)
            .leftOuterJoin(LOCATION2PLAYER)
                .on(LOCATION2PLAYER.LOCATION_ID.eq(LOCATION.LOCATION_ID))
        .orderBy(LOCATION.PLAYER_ID)
        .fetchResultSet())  
    Stream<Location> stream = jdbcMapper.stream(rs);


然后在流上做你需要做的,你也可以得到一个迭代器。

【讨论】:

重要的是要注意这个解决方案绕过了 Jooq 的记录映射器并直接进入结果集。如果您有任何 Jooq 力类型,这将是有问题的 - 您必须为 SFM 重新定义它们 我的理解是强制类型是为了生成Record对象。您可以使用 sfm 使用强制类型映射到 jOOQ 记录对象。那么问题是如何将连接映射到 Record 对象?您将需要使用 Tuple2> 我通过创建 ConverterFactoryProducer 并通过遍历 jooq 转换器列表并将它们的方法传递给 constantConverter() 来创建转换器来解决强制类型问题。如果你有兴趣,我会添加一个要点。不过还没有机会彻底测试它。 有趣的是,你介意打开一张票,然后再详细一点,我想我并没有真正理解这个问题

以上是关于具有一对多和多对多关系的 JOOQ pojos的主要内容,如果未能解决你的问题,请参考以下文章

多表关系一对多和多对多

Hibernate映射关系:一对一对多和多对多

如何在 DynamoDB 中建模一对一、一对多和多对多关系

同一实体之间的一对多和多对多关系

MySQL——一对多和多对多简单模型建表

MySQL——一对多和多对多简单模型建表