spring-data-jpa 3-way-intersection 表

Posted

技术标签:

【中文标题】spring-data-jpa 3-way-intersection 表【英文标题】:spring-data-jpa 3-way-intersection table 【发布时间】:2019-10-29 09:08:23 【问题描述】:

我将尝试说明我近期想要实现的目标... 假设我有一个用户表:

USER_INFO
  USER_ID [PK]
  USER_NAME
  PASSWORD

为每个用户定义连接的交集表 (N:M - ManyToMany)

CONNECTION_INFO
  CONNECTION_ID [PK]
  USER_A_ID [FK - references USER_INFO(USER_ID)]
  USER_B_ID [FK - references USER_INFO(USER_ID)]
  CONNECTION_TYPE_ID [FK - references CONNECTION_TYPE(CONNECTION_TYPE_ID)]

CONNECTION_TYPE 很简单:

CONNECTION_TYPE
  CONNECTION_TYPE_ID [PK]
  CONNECTION_TYPE_NAME [CHECK allowed values are: FRIEND, FAMILY, ...]

在 Spring 方面,我将我的用户实体定义为:

@Entity
@Table(name = "USER_INFO")
public class User implements Serializable 
  @Id
  @NotNull
  @Column(name = "USER_ID")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer userId;

  @Column(name = "USER_NAME)
  private String userName;

  @Column(name = "PASSWORD)
  private char[] password;

  @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(name = "CONNECTION_INFO",
             joinColumns =  @JoinColumn(name = "USER_A_ID") ,
             inverseJoinColumns =  @JoinColumn(name = "USER_B_ID") )
  private List<User> connections;

  // ctor, getters, setters, toString, ...

我有一个扩展 JpaRepository 等的 UserRepository 接口。现在,它完美运行,我可以检索所有连接,无论是 FRIEND、FAMILY、MOST_HATED_PERSONS、BLOCKED、DEMON 等...

我也尝试在图片中集成 ConnectionType...

@Entity
@Table(name = "CONNECTION_TYPE")
public class Connection implements Serializable 
  public static enum Types 
    FRIEND, FAMILY, BLOCKED, ...
  

  @Id
  @NotNull
  @Column(name = "CONNECTION_TYPE_ID")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer connectionTypeId;

  @Column(name = "CONNECTION_TYPE_NAME")
  private ConnectionType connectionType;

  // ctor, getters, setter, etc

现在,我的问题是,如何根据 Connection.Types 仅获取给定用户的特定连接?例如,我只想找到 FRIENDs 或 FAMILY,我想你明白我的意思。这个 3 路交叉表让我很头疼。

@澄清: 我想要的是在我的用户实体上定义的@ManyToMany 关系,它恰好有额外的列。我知道在这种情况下,有人提出了像LINK 这样的解决方案。在我的情况下,这个额外的列是第三个表的外键(USER_INFO(保存用户),CONNECTION_INFO(保存用户之间的连接N:M +有关连接类型的信息),CONNECTION_TYPE。如果我可以用它建模spring-data-jpa 据我了解,我只需要 UserRepository 下的一个神奇的命名方法,类似于(完全不正确):

public interface UserRepository extends JpaRepository<User, Integer> 
    List<User> findUserFriendsByConnectionType(User userWhoseFriendsWeAreSearching, String connectionTypeFromTheThirdTable);

这就是我想要的。我知道通过为交集表创建一个实体并将ManyToMany分解为OneToMany和ManyToOne,使用普通的额外列很简单,只是碰巧我有第三个表和一个可能的ManyToOne(1个连接可以有1个关联类型,而一个type 可以链接到任意数量的连接)在与 connection_type 表的交集实体上。

我希望它能清除一切。以上只是我从未想过我们会挂断枚举的示例,因为我想让它看起来简单,我可能让它太简单了:)。

【问题讨论】:

如果附加表只有固定值,为什么还要附加表?它不是一个枚举,而是一个实体。因此,您应该有一个实体,而不是枚举 ConnectionType。您当前的设置将完全失败,因为 connection_info 表没有这两列。 哇好棒……我完全搞砸了这个命名……我希望我现在把它清理干净了。我没有交集表的实体:)。关于您的第一点,我的附加表能够定义新的连接类型。 您不能只定义新的连接类型,因为您已将它们硬编码在 java 代码中的枚举中。因此,它也需要更改代码。您的 Connection 应该有一个 @Table 和 [@SecondaryTable](docs.oracle.com/javaee/6/api/javax/persistence/…) table annotation if you want to map 1 entity to multiple tables. Your User` 应该有一个 Connection 实体列表而不是 User 实体。然后你可以编写一个特定的查询类型。 这只是一个例子......基本上我想要的是一个多对多关系,它带有一个额外的列,恰好是第三个表的 ForeignKey。然后在存储库中,我想定义一个函数,如:findUserConnectionsByConnectionTypeConnectionTypeName 或类似的函数,如果我正确获取书籍,则由 spring-boot 自动生成。我不知道您要链接我的这个 SecondaryTAble 是什么,但我 90% 确定我不需要它。 您有 3 个表和 2 个实体。因此,要么您必须添加一个实体,要么包含要映射到 2 个表的 1 个实体的附加映射(@SecondaryTable 注释)。简单地为User 实体中的用户列表添加@ManyToMany 并不能完成这项工作。那必须是 Connection 的列表,它要么链接了 ConnectionType 实体,要么是 2 个表的聚合。 【参考方案1】:

我设法解决了这个问题,但我不确定这是否是正确的方法。无论如何,这是我的解决方案。考虑以下 3 个表:

create table USER_INFO (
  USER_ID int not null primary key,
  USER_NAME varchar(16),
  PASSWORD varchar(64)
);

create table CONNECTION_TYPE (
  CONNECTION_TYPE_ID int not null primary key,
  CONNECTION_TYPE_NAME varchar(16) not null,
  CONNECTION_TYPE_DESCRIPTION varchar(128),
  unique (CONNECTION_TYPE_NAME)
);

create table CONNECTION (
  CONNECTION_ID int not null primary key,
  CONNECTION_TYPE_ID int,
  RELATED_USER_ID int,
  RELATING_USER_ID int,
  foreign key (CONNECTION_TYPE_ID) references CONNECTION_TYPE(CONNECTION_TYPE_ID),
  foreign key (RELATED_USER_ID) references USER_INFO(USER_ID),
  foreign key (RELATING_USER_ID) references USER_INFO(USER_ID)

通过上面的 3 个表,我想提供一个功能来根据连接的类型为任何给定用户获取连接。为此,我创建了 3 个实体,如下所示:

@Entity
@Table(name = "CONNECTION_TYPE")
public class ConnectionType implements Serializable 
  @Id
  @NotNull
  @Column(name = "CONNECTION_TYPE_ID")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer connectionTypeId;

  @NotNull
  @Column(name = "CONNECTION_TYPE_NAME", unique = true)
  private String connectionTypeName;

  @Column(name = "CONNECTION_TYPE_DESCRIPTION")
  private String connectionTypeDescription;

  ...

这里没有什么特别有趣的地方,我省略了构造函数、getter、setter 等,并且在 ConnectionType 中我不想为这种类型的所有连接映射,因此方向不存在。

@Entity
@Table(name = "CONNECTION")
public class Connection implements Serializable 
  @Id
  @NotNull
  @Column(name = "CONNECTION_ID")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer connectionId;

  @NotNull
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "CONNECTION_TYPE_ID", referencedColumnName = "CONNECTION_TYPE_ID")
  private ConnectionType connectionType;

  @NotNull
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "RELATED_USER_ID", referencedColumnName = "USER_ID")
  private User relatedUser;

  @NotNull
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "RELATING_USER_ID", referencedColumnName = "USER_ID")
  private User relatingUser;

  ...

如果没有其他人至少对我来说,这个更有趣。这将是我的交集表实体。使用的 ConnectionType 与 ManyToOne 存在单向映射,因为一个 Connection 可以只有一个 ConnectionType,而相同的 ConnectionType 可以用于任意数量的 Connection。 其他 2 个用户映射我确定我搞砸了,但在此之前这是用户实体:

@Entity
@Table(name = "USER_INFO")
public class User implements Serializable 
  @Id
  @NotNull
  @Column(name = "USER_ID")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer userId;

  @NotNull
  @Column(name = "USER_NAME")
  private String userName;

  @NotNull
  @Column(name = "PASSWORD")
  private char[] password;

  @OneToMany(fetch = FetchType.LAZY, mappedBy = "relatedUser", cascade = CascadeType.ALL, orphanRemoval = true)
  private List<Connection> connections;

现在我更加确定我完全搞砸了,但我会显示实际错误。我的存储库很简单:

@Repository
public interface UserRepository extends JpaRepository<User, Integer> 

我有一个带有简化 findAllConnectionsForUserById 函数的 UserService:

@Service
public interface UserService 
    List<User> findAllConnectionsForUserById(Integer userId);

方法实现很简单:

@Override
@Transactional
public List<User> findAllConnectionsForUserById(Integer userId) 
    Optional<User> _user = userRepository.findById(userId);
    // omitted exception handling...
    User user = _user.get();
    List<Connection> connections = user.getConnections();

    return connections.strea.map(Connection::getRelatingUser).collect(Collectors.toList());

这种方式似乎适用于简单的情况,如果我也采用 ConnectionType:

connections.stream().filter(c -> c.getConnectionType().getConnectionTypeName().equals("FRIEND")).map(Connection::getRelatingUser).collect(Collectors.toList());

它似乎也有效。同样,不确定这是否是正确的方法,但至少它可以完成工作。

【讨论】:

您为什么需要为此提供服务?您可以在 User 上创建 2 个方法,它们会遍历 Connection 实体的集合以实现相同的目的。或者编写一个查询来执行此操作,特别是对于使用查询仅获取您需要的内容的大型数据集将优于内存处理。

以上是关于spring-data-jpa 3-way-intersection 表的主要内容,如果未能解决你的问题,请参考以下文章

Spring-data-jpa:批量插入不起作用

spring-data-jpa 1.11.16 带游标的存储过程

Spring-data-jpa详解

Spring-data-jpa 学习笔记

Spring-data-jpa + Hibernate 未创建预期表

SpringBoot整合spring-data-jpa