简体   繁体   English

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

[英]JOOQ pojos with one-to-many and many-to-many relations

I am struggling to understand how to handle pojos with one-to-many and many-to-many relationships with JOOQ.我正在努力理解如何通过 JOOQ 的一对多和多对多关系处理 pojo。

I store locations that are created by players (one-to-many relation).我存储由玩家创建的位置(一对多关系)。 A location can hold multiple additional players who may visit it (many-to-many).一个位置可以容纳多个可能访问它的额外玩家(多对多)。 The database layout comes down to the following:数据库布局归结为以下几点:

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;

Within my java application, all these informations are stored within one pojo.在我的 Java 应用程序中,所有这些信息都存储在一个 pojo 中。 Note that the player and the list of invited players can be updated from within the application and need to be updated in the database as well:请注意,玩家和受邀玩家列表可以从应用程序中更新,也需要在数据库中更新:

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…
}

Can I use JOOQ's pojo mapping to map these three records into the single pojo?我可以使用 JOOQ 的 pojo 映射将这三个记录映射到单个 pojo 中吗? Can I use JOOQ's CRUD feature from this pojo to update the one-to-many and many-to-many relations?我可以使用这个 pojo 的 JOOQ 的 CRUD 功能来更新一对多和多对多关系吗? If the pojo mapping cannot be used, can I take advantage of JOOQ in any way except using it to write my SQL statements?如果无法使用 pojo 映射,除了使用它来编写我的 SQL 语句之外,我可以以任何方式利用 JOOQ 吗?

Using MULTISET for nested collections with jOOQ 3.15使用MULTISET与jOOQ 3.15嵌套集合

Starting from jOOQ 3.15, you can use the standard SQL MULTISET operator to nest collections, and to abstract over the below SQL/XML or SQL/JSON serialisation format.从 jOOQ 3.15 开始,您可以使用标准的 SQL MULTISET运算符来嵌套集合,并抽象以下 SQL/XML 或 SQL/JSON 序列化格式。 Your query would look like this:您的查询将如下所示:

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);

If your DTOs were immutable (eg Java 16 records), you can even avoid using reflection for mapping, and map type safely into your DTO constructors using constructor references and the new jOOQ 3.15 ad-hoc conversion feature .如果您的 DTO 是不可变的(例如 Java 16 记录),您甚至可以避免使用反射进行映射,并使用构造函数引用和新的 jOOQ 3.15 临时转换功能将类型安全地映射到 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 有关MULTISET更多详细信息,另请参阅此博客文章

Using SQL/XML or SQL/JSON for nested collections with jOOQ 3.14在 jOOQ 3.14 中对嵌套集合使用 SQL/XML 或 SQL/JSON

Starting from jOOQ 3.14, it's possible to nest collections using SQL/XML or SQL/JSON, if your RDBMS supports that.从 jOOQ 3.14 开始,如果您的 RDBMS 支持,可以使用 SQL/XML 或 SQL/JSON 嵌套集合。 You can then use Jackson, Gson, or JAXB to map from the text format back to your Java classes.然后,您可以使用 Jackson、Gson 或 JAXB 从文本格式映射回 Java 类。 For example:例如:

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));

In some database products, like PostgreSQL, you could even use SQL array types using ARRAY_AGG() and skip using the intermediate XML or JSON format.在某些数据库产品中,例如 PostgreSQL,您甚至可以使用ARRAY_AGG()使用 SQL 数组类型,并跳过使用中间 XML 或 JSON 格式。

Note that JSON_ARRAYAGG() aggregates empty sets into NULL , not into an empty [] .请注意, JSON_ARRAYAGG()将空集聚合到NULL ,而不是聚合到空[] If that's a problem, use COALESCE()如果这是一个问题,请使用COALESCE()

Historic answer (pre jOOQ 3.14)历史答案(前 jOOQ 3.14)

jOOQ doesn't do this kind of POJO mapping out of the box yet, but you can leverage something like ModelMapper which features a dedicated jOOQ integration , which works for these scenarios to a certain extent. jOOQ 还没有开箱即用地进行这种 POJO 映射,但您可以利用ModelMapper 之类的东西,它具有专用的jOOQ 集成,在一定程度上适用于这些场景。

Essentially, ModelMapper hooks into jOOQ's RecordMapper API.本质上,ModelMapper 挂钩到 jOOQ 的RecordMapper API。 More details here:更多细节在这里:

You can use SimpleFlatMapper on the ResultSet of the query.您可以在查询的 ResultSet 上使用SimpleFlatMapper

create a mapper with player as the key创建一个以玩家为键的映射器

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

Then use fetchResultSet to get the ResultSet and pass it to the mapper.然后使用 fetchResultSet 获取 ResultSet 并将其传递给映射器。 Note that it is important to orderBy(LOCATION.PLAYER_ID) otherwise you might end up with split Locations.请注意 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);

}

then do what you need to do on the stream, you can also get an iterator.然后在流上做你需要做的事情,你也可以得到一个迭代器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM